@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)
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: