'How to get IP address and port of newly accepted connection in Python asyncio server?

I'm using the asyncio library in Python 3.8 https://docs.python.org/3/library/asyncio.html

I am creating a server, and in the "newly accepted connection" callback function, I want to find out the remote IP address and port of the new client.

The arguments to the callback function are one instance each of StreamReader and StreamWriter used to read and write from the client. Is there a straightforward way to find the IP address and port of the streams? Note that I want to do this for both SSL and non-SSL connections.

Here I create the server:

async def create_server(self, new_client_cb, host, port):
    srvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srvsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srvsocket.bind((host, port))
    srvsocket.listen(5)
    return await asyncio.start_server(new_client_cb, sock=srvsocket, start_serving=False)

I pass in the callback function, which adheres to the documentation and accepts an instance of a StreamReader and a StreamWriter.

Here is said callback function. It's part of a class, hence the leading self argument.

async def _new_client(self, client_r, client_w):
    try:
        self.logger.debug("New client on incoming proxy")
        dests_r = {}
        dests_w = {}
        for addr in self.config['addrlist']:
            host, port = addr.split(':')
            host = socket.gethostbyname(host)
            self.logger.debug(f"Connecting to {addr}...")
            r, w = await self.protocol.open_connection(host, port)
            self.logger.debug(f"Connected to {addr}")
            dests_r[addr] = r
            dests_w[addr] = w
        done, pending = await asyncio.wait(
            [self._tunnel(list(dests_r.values()), [client_w]), self._tunnel([client_r], list(dests_w.values()))],
            return_when=asyncio.FIRST_EXCEPTION
        )
        for result in done:
            if result.exception():
                raise result.exception()
    except Exception as e:
        self.logger.error(f"Caught exception: {str(e)}")
        traceback.print_exc()

There's a lot going in that function related to other aspects of my application. I think my question ultimately boils down to: how do I found out the remote address and port associated with the new client given these inputs, the StreamReader and StreamWriter? I'm looking into asyncio's Transport classes: https://docs.python.org/3/library/asyncio-protocol.html but perhaps others can point me in the right direction.

Wrt asyncio's Transport classes, I can see that they allow you to query "extra" information via the get_extra_info(str) function, eg:

client_r._transport.get_extra_info('socket')

Okay, this works for non-encrypted (non-SSL) traffic. But I can't query the socket on an encrypted transport. I can only get the SSL object:

https://docs.python.org/3/library/ssl.html#ssl.SSLObject

This object provides an attribute "server_hostname" which will give me the hostname/IP that was used to connect, so at this point I just need the port.



Solution 1:[1]

Ok, I was able to figure it out eventually.

I really just needed to pass a different key to get_extra_info

Both SSL and non-SSL transports support the "peername" key

So I modified my code to the following:

client_r._transport.get_extra_info('peername')
client_w._transport.get_extra_info('peername')

A separate issue I was running into is that I was querying the 'peername' key after the Stream had been closed, and so I was getting None back.

More information on get_extra_info can be found in the asyncio documentation:

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Daniel F