From: Mircea Ulinic Date: Wed, 1 Feb 2017 12:02:36 +0000 (+0000) Subject: Support also source_port X-Git-Tag: v4.5.0~39^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9cec2f7e5980640d79e54420776020bb02eb53ef;p=thirdparty%2Ftornado.git Support also source_port --- diff --git a/tornado/tcpclient.py b/tornado/tcpclient.py index bf9b94bb2..1e2891745 100644 --- a/tornado/tcpclient.py +++ b/tornado/tcpclient.py @@ -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: