1

If I have a Twisted server, how can I find its public-facing IP address?

Take this trivial echo server example:

from twisted.internet import protocol, reactor, endpoints

class Echo(protocol.Protocol):
    def dataReceived(self, data):
        self.transport.write(data)

class EchoFactory(protocol.Factory):
    def buildProtocol(self, addr):
        return Echo()

server_endpoint = endpoints.serverFromString(reactor, "tcp:1234")
listening_port_deferred = server_endpoint.listen(EchoFactory())
reactor.run()

I was expecting something like server_endpoint.getHost(), but I can't see that TCP4ServerEndpoint offers anything useful.

By adding the following lines before reactor.run(), we can see that the server is listening on all interfaces (0.0.0.0):

def print_host(listening_port):
    print("listening_port.getHost():", listening_port.getHost())
listening_port_deferred.addCallback(print_host)

It outputs listening_port.getHost(): IPv4Address(type='TCP', host='0.0.0.0', port=1234). But that doesn't help us with the IP address of the network interface of the server.

We can get the IP address of the client by adding the following as the first line of buildProtocol():

print("Client's address:", addr.host)

But that only gives us the client's address.

How should I get the server's IP address?

10 Rep
  • 2,217
  • 7
  • 19
  • 33
Matthew Walker
  • 2,527
  • 3
  • 24
  • 30
  • A similar question was asked [here](https://stackoverflow.com/questions/35205645/find-my-server-ip-address-with-twisted/47064911), but the self-answer doesn't actually answer their own question and instead uses `socket`. – Matthew Walker Jul 13 '20 at 04:28
  • If the server is listening on `0.0.0.0` it means it's listening on all interfaces and the server IP could be anyone of those. It makes sense that you can get the IP from the client because it connects on a specific IP. – notorious.no Jul 13 '20 at 12:52
  • What do you mean by "public-facing IP address"? Consider that the host the server is running on may be behind some kind of NATing router (yes, even servers get NAT'd - consider AWS EC2 VPCs, use of which is often considered best practice). – Jean-Paul Calderone Jul 13 '20 at 13:37
  • @Jean-PaulCalderone Your example was bang on the money; that's exactly my intended host and I did not even consider that they would be behind a NATing router. However, I would have thought it possible to at least get the internal IP address of the server (if not public-facing, then NATing-router-facing). Is that possible under Twisted? – Matthew Walker Jul 13 '20 at 21:03
  • @notorious.no You're right. And once the client has made a connection they've connected to a specific network interface on the server. How do you get to **that** IP address under Twisted? – Matthew Walker Jul 13 '20 at 21:05

2 Answers2

1

Twisted will tell you the address you've bound the server to using just the method you found, getHost on the listening port. Unfortunately, it has the big limitation that you found which is that when the server is listening on all local addresses (INADDR_ANY) it gives you 0.0.0.0 (the canonical IPv4 dotted-quad representation of INADDR_ANY).

When this happens, you have to go outside of Twisted. I've found the netifaces package to be pretty good for this. From the docs:

>>> netifaces.interfaces()
['lo0', 'gif0', 'stf0', 'en0', 'en1', 'fw0']
>>> >>> addrs = netifaces.ifaddresses('lo0')
>>> addrs[netifaces.AF_INET]
[{'peer': '127.0.0.1', 'netmask': '255.0.0.0', 'addr': '127.0.0.1'}]

By combining this information with the observation that 0.0.0.0 means "all local addresses" you can figure out what local addresses the server will accept connections on.

10 Rep
  • 2,217
  • 7
  • 19
  • 33
Jean-Paul Calderone
  • 47,755
  • 6
  • 94
  • 122
1

Thanks to notorious's comment, I realised that the server's IP address is available only once a client connects. However, as Jean-Paul points out, this IP address isn't necessarily public-facing and may well be behind a NATing router.

To obtain the server's IP address for a given connection, we can use the getHost() method from the transport attribute of the Protocol class. This is documented in the ITransport interface.

For example, if we add the following method into the Echo protocol class in the original question, each time a client connects, the server will print out the IP address that was used.

def connectionMade(self):
    print("IP address of host given connection:", self.transport.getHost())

So, for example, if you connect from the same machine on which the server is running, you will see:

IP address of host given connection: IPv4Address(type='TCP', host='127.0.0.1', port=1234)

However, if you connect from another machine on the same network, you might see:

IP address of host given connection: IPv4Address(type='TCP', host='192.168.5.103', port=1234)

10 Rep
  • 2,217
  • 7
  • 19
  • 33
Matthew Walker
  • 2,527
  • 3
  • 24
  • 30