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?
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.
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)
Related
A project I am working on has an Android app as a front-end and a Python program that would be used as the back-end.
I want to send data from the Android app (primarily images) to the Python program, do some processing and send the result back to the Android app.
I have found numerous tutorials that suggest using the socket module in python to create the server side, but all tutorials show the server on local network only (For testing purposes I created the client side also in Python, but it would be converted to Java later on)
The server code:
from requests import get
import socket
public_ip = get('https://api.ipify.org').text
print('My public IP address is: {}'.format(public_ip))
# getting the hostname by socket.gethostname() method
hostname = socket.gethostname()
# getting the IP address using socket.gethostbyname() method
local_ip = socket.gethostbyname(hostname)
# printing the hostname and ip_address
print(f"Hostname: {hostname}")
print(f"IP Address: {local_ip}")
#
HOST = local_ip
PORT = 80 # Port to listen on (non-privileged ports are > 1023)
with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024).decode('utf-8')
if not data:
break
conn.sendall(data.encode('utf-8'))
The client code:
import socket
HOST = '…' # I modify it to the server's public IP address, as printed from the server code
PORT = 80 # The port used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
with socket.create_connection((HOST, PORT)) as s:
s.sendall(b'Hello, world')
data = s.recv(1024)
print('Received', repr(data))
Using the code above, if I try using any port other than 80 I get ConnectionRefusedError: [Errno 111] Connection refused. And for port 80, I get TimeoutError: [Errno 110] Connection timed out.
In both cases, I try to connect from a device on another network.
I tried to use the ping command in Windows CMD to check the connection to the server, and I get 'connection refused message'.
I understand that the Firewall is what probably blocks the connection, but I don't know how to bypass it. I added a new rule in the Inbound Rules section (as suggested on other websites) but for no avail… The results were the same.
How can I make the connection between remote devices on different networks?
Thanks in advance ☺
In order to connect to your server using a TCP socket connection, you need to make sure your server can listen on a port on a publically available IP address.
If the External IP address is assigned to your computer directly,
and if you run the server code on that computer, then the TCP port opened by the server code should be available on the internet.
However, IP addresses are often assigned to a modem/router in home networks,
instead of assigning them to any connected device directly.
To find out if your External IP address is assigned to the computer directly you can use tools that your OS support (eg. ipconfig on windows). If you can see the IP address returned by api.ipify.org, then it means your computer is connected directly. You can change your code to connect using publically exposed IP:
HOST = public_ip
If this is successful means your computer is assigned an external address directly. Which is highly unlikely.
There are several workarounds for this problem though:
1) Configure your router to forward port
Configure your router to forward all connections to it's external TCP port, to an internal host in your network which is assigned to your computer. Please find instructions how it is done for your router.
2) Setup a remote proxy
If you don't have permission to change your router settings you can set up a remote proxy listening on the TCP port. While there is a number of ways of doing this, very popular is to set up a remote SSH tunnel, for that you need to have a server with SSH access and an external IP. Run this command:
ssh -R 80:localhost:8080 root#your-ssh-server-host
You can also use a third-party service that exposes your private host on the internet like:
Ngrok (Commercial, with free plans)
Localtunnel (Open Source, can be self-hosted)
I have an Asyncio script I'm writing. Everything is working but I had a question about what I'm seeing in a tuple asyncio returns as the address.
This line of code returns two different things depending on weather I connect with a client using localhost or my local IP address.
(Server code)
addr = writer.get_extra_info('peername')
print("Received %r from %r" % (message, addr))
With localhost as my connection in my client, I see this on my server
(Client code)
reader, writer = await asyncio.open_connection('localhost', 8888, loop=asyncloop)
(Server prints)
Received 'Hello World!' from ('::1', 50402, 0, 0)
And with an IP address as my connection in my client,
(Client code)
reader, writer = await asyncio.open_connection('192.168.147.200', 8888, loop=asyncloop)
(Server prints)
Received 'Hello World!' from ('192.168.147.139', 50313)
What are the meanings of the two zero's in the first tuple? And why aren't they there when I connect with an IP?
See https://docs.python.org/3.5/library/socket.html?highlight=socket#socket-families
Your localhost connection is arriving via IPv6:
For AF_INET6 address family, a four-tuple (host, port, flowinfo, scopeid) is used, where flowinfo and scopeid represent the sin6_flowinfo and sin6_scope_id members in struct sockaddr_in6 in C. For socket module methods, flowinfo and scopeid can be omitted just for backward compatibility. Note, however, omission of scopeid can cause problems in manipulating scoped IPv6 addresses.
Your explicit IP address connection is an IPv4 connection, for which:
A pair (host, port) is used for the AF_INET address family, where host is a string representing either a hostname in Internet domain notation like 'daring.cwi.nl' or an IPv4 address like '100.50.200.5', and port is an integer.
The hostname 'localhost' can be resolved to either an IPv6 or IPv4 address, whereas '192.168.147.200' is an explicit IPv4 address.
Additional fields seems to be related about the IPv6 address scoping.
See also socket.getnameinfo and socket.getpeername.
Title says all. I have a client and a server setup, but they only work with localhost. How can I connect to the socket from a different network?
Client
# Echo client program
import socket
print "Client"
HOST = "localhost" # Symbolic name meaning all available interfaces
PORT = 5001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
while True:
data2 = raw_input()
s.sendall(data2)
Server
# Echo server program
import socket
print "Server"
HOST = "" # Symbolic name meaning all available interfaces
PORT = 5001 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
data = conn.recv(1024)
print data
if data == "Ping":
print "Pong!"
conn.close()
Check your public ip address using online tools like http://whatismyipaddress.com/
Check your local ip address using ipconfig -a in windows ifconfig in linux.
Now if you are behind a dsl router (generally, you are) these addresses are completely different. So you have to tell your router to send whenever a connection attemp received to a TCP port XXXX , forward it to "My Machine" (called Port Forwarding ). Although port forwarding settings are similar in most routers, there is no standard (Generally under NAT / Port Forwarding menu items on your router web configuration interface ). You may have to search instructions for your specific model.It's a good idea to set your computer to use a static ip address before port forwarding.Otherwise, the settings will be invalid if your computer is assigned another IP adress via DHCP.
If port forwarding is successful, now you only have to set your client application to connect to your public ip address. In your specific situation it's HOST = "X.X.X.X" in your client source code. Check if port forwarding works with a socket tester application you downloaded from somewhere. ( Don't test it with your experimental code, use an application you are sure that it's working). All did not work, check out the note below. It's the last resort ,though.
Note : Some ISP's put their clients behind an extra firewall for security. A simple method to detect if this is the situation is , your Wan ip address you see in your router web interface will be different from what you see in online tools like whatsmyip. In this situation no matter what you do , you will not be able to connect. You have to call your ISP and tell them to disable firewall for your connection . You may have some difficulties to explain them what you are talking about :).
The code:
import socket, threading
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("my ipv4 from ipconfig", 12))
server.listen(5)
def client_handler(client_socket):
request = client_socket.recv(100)
print "[*] Received: " + request
client_socket.close()
while True:
client, addr = server.accept()
print "[*] Accepted connection from: %s:%d" % (addr[0], addr[1])
servert = threading.Thread(target=client_handler, args=(client,))
servert.start()
So the server seems to work fine locally but if i ask my friend on a different network to connect, it doesn't connect. I tried port forwarding from the router
http://i.stack.imgur.com/wSUir.png ( cant post img apparently cause my_reputation < 10)
I also tried using the ip i get from the whatismyip website, but i get the error:
error: [Errno 10049] The requested address is not valid in its context
Any ideas on what i could do so other people can connect? Thanks.
When you're trying to access your server from outside your LAN, you ought to use 0.0.0.0 as your IP address while creating your socket object. 0.0.0.0 usually means the default route (the route to "the rest of" the internet, aside from routes in your local network etc.). If you use the IP address allotted by DCHP (in your case, the router), the devices connected to the network (router) is only aware of the fact that your IP address is what you get in $ifconfig command, a private IP address which the client is unaware
I am using an asyncore.dispatcher client on python to connect to a server developed in LabWindows running on a PC. Here's the code snippet on the client that connects to the server:
class DETClient(asyncore.dispatcher):
def __init__(self, host, port):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host,port))
On the server side, my Labwindows code is looking for two parameters, TCPPeerName and TCPPeerAddr:
GetTCPPeerName (handle, peerName, sizeof (peerName));
GetTCPPeerAddr (handle, peerAddress, sizeof (peerAddress));
It seems that the python code is not passing the hostname at all, because my server gets a NULL for PeerName.
Do I have to do anything to specifically make the asyncore client to send the PeerName when establishing a connection?
Do I have to do anything to specifically make the asyncore client to send the PeerName when establishing a connection?
No, you don't, because TCP clients don't send names when establishing a connection. They send addresses.
GetTCPPeerName is almost certainly calling gethostbyaddr(X), where X is the address returned by GetTCPPeerAddr. In your case gethostbyaddr() is failing because the information is not available.
This means that your hostname resolution database is missing some data -- you might need to update your DNS, your hosts file, your WINS data, or wherever your host name data lives.
IS that DETClient the actual implementation? Because it's missing instantiations of both host and port for that instance?
in self.connect you're using self.host and self.port, which both evaulate to None since you havent set them yet.
class DETClient(asyncore.dispatcher):
def __init__(self, host, port):
self.host = host #<---- this is missing
self.port = port #<---- this is missing
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((self.host,self.port))