]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Support also source_port
authorMircea Ulinic <mirucha@cloudflare.com>
Wed, 1 Feb 2017 12:02:36 +0000 (12:02 +0000)
committerMircea Ulinic <mirucha@cloudflare.com>
Wed, 1 Feb 2017 12:06:14 +0000 (12:06 +0000)
tornado/tcpclient.py

index bf9b94bb265f07372d5af6eb02238bcd8c8e075b..1e289174525d610891585c3f2223c33334bc8e2f 100644 (file)
@@ -155,23 +155,26 @@ class TCPClient(object):
 
     @gen.coroutine
     def connect(self, host, port, af=socket.AF_UNSPEC, ssl_options=None,
-                max_buffer_size=None, source_ip=None):
+                max_buffer_size=None, source_ip=None, source_port=None):
         """Connect to the given host and port.
 
         Asynchronously returns an `.IOStream` (or `.SSLIOStream` if
         ``ssl_options`` is not None).
 
-        Using the `source_ip` kwarg, one can specify the source
+        Using the ``source_ip`` kwarg, one can specify the source
         IP address to use when establishing the connection.
         In case the user needs to resolve and
         use a specific interface, it has to be handled outside
         of Tornado as this depends very much on the platform.
+
+        Similarly, when the user requires a certain source port, it can
+        be specified using the ``source_port`` arg.
         """
         addrinfo = yield self.resolver.resolve(host, port, af)
         connector = _Connector(
             addrinfo, self.io_loop,
             functools.partial(self._create_stream, max_buffer_size,
-                              source_ip=source_ip)
+                              source_ip=source_ip, source_port=source_port)
         )
         af, addr, stream = yield connector.start()
         # TODO: For better performance we could cache the (af, addr)
@@ -182,17 +185,26 @@ class TCPClient(object):
                                             server_hostname=host)
         raise gen.Return(stream)
 
-    def _create_stream(self, max_buffer_size, af, addr, source_ip=None):
+    def _create_stream(self, max_buffer_size, af, addr, source_ip=None,
+                       source_port=None):
         # Always connect in plaintext; we'll convert to ssl if necessary
         # after one connection has completed.
-        if source_ip:
-            # If source_ip is needed, will try binding.
-            # If it fails, will exit loudly.
-            self.socket.bind((source_ip, 0))
-            # Does not bind try binding to a specific port,
-            # so the port argument is set to 0.
+        source_port_bind = source_port if isinstance(source_port, int) else 0
+        source_ip_bind = source_ip
+        if source_port_bind and not source_ip:
+            # User required a specific port, but did not specify
+            # a certain source IP, will bind to the default loopback.
+            source_ip_bind = '::1' if af == socket.AF_INET6 else '127.0.0.1'
+            # Trying to use the same address family as the requested af socket:
+            # - 127.0.0.1 for IPv4
+            # - ::1 for IPv6
+        socket_obj = socket.socket(af)
+        if source_port_bind or source_ip_bind:
+            # If the user requires binding also to a specific IP/port.
+            socket_obj.bind((source_ip_bind, source_port_bind))
+            # Fail loudly if unable to use the IP/port.
         try:
-            stream = IOStream(socket.socket(af),
+            stream = IOStream(socket_obj,
                               io_loop=self.io_loop,
                               max_buffer_size=max_buffer_size)
         except socket.error as e: