2
votes

I'm trying to have a two way communication using UDP between a server (on public IP) and a client(across a NAT). My logic says that if server sends some data to the IP and the port it received the packet from, the client should still get it, because NAT would have the mapping to send the packet to the client ultimately.

Client has 2 processes, one to send packets, another process to receive the data. Server keeps on waiting for data, and if it gets data, it sends data back to the Port and IP where from the data was received.

Client code below:

client_recv.py

import socket
import sys

UDP_IP = '0.0.0.0'#my ip address in the local network
UDP_PORT = 5000

sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((UDP_IP, UDP_PORT))

while True:
    data, addr = sock.recvfrom(1024)
    print ("received message:" + data)

client_send.py

import socket
import time
import sys

UDP_IP = 'XXXXXXX.com'#external server IP address
UDP_PORT = 4000
MESSAGE = "Hi!"

sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 5000))
i = 0
while True:
    sock.sendto(MESSAGE + ' # ' + str(i), (UDP_IP, UDP_PORT))
    time.sleep(1)
    i = i + 1

The server (on XXXXX.com - public IP) gets the packets, but not the client. Below is the code to receive and send packets on server:

server_send_recv.py

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', 4000))

while True:
    data, inetaddr = sock.recvfrom(1024)
    print('Received ' + data)
    (ip, port) = inetaddr
    print("IP:"+str(ip)+",Port:"+str(port))
    sock.sendto('Hi From Server (against ' + data + ')', (ip, port))

EDIT 1:

As already stated in the answer, the same socket has to be used, so I did the following, and it worked. However, I'm still not clear why a different socket (client_recv), which was bound to the same Port on client would not work. Since the same ports are being reused, the client_recv should have worked right? Why did it fail, and what made the same socket to send and receive on client work?

client_send_recv.py

import socket
import time
import sys

UDP_IP = 'twig-me.com'#external server IP address
UDP_PORT = 4000
MESSAGE = "Hi!"

sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 5000))
i = 0
while True:
    print ("Sending message:" + str(i))
    sock.sendto(MESSAGE + ' # ' + str(i), (UDP_IP, UDP_PORT))
    time.sleep(1)
    i = i + 1
    data, addr = sock.recvfrom(1024)
    print ("received message:" + data)
1
I'm not sure what is different in your setup. But your code works for me without changes (apart from local and target IP). Both clients on same Linux machine behind NAT, server on Linux machine too. There is one interesting thing though: one of your clients binds to the local IP while the other binds to 0.0.0.0. Depending on your OS this might make a difference. I suggest to bind both clients not only to the same port but also to the same IP.Steffen Ullrich
No its binded both clients to '0.0.0.0'. Why do you say both are bound to different IP, one to local, other to 0.0.0.0?Ouroboros
@Ouroboros: no, the first client is not bound to 0.0.0.0. At least I've interpreted UDP_IP = '0.0.0.0'#my ip address in the local network as an order to replace the actual IP there.Steffen Ullrich
I'm using linux on both machines, and most likely there's no firewall (Client is on Raspian OS on pi)Ouroboros
@SteffenUllrich I'm able to receive the packets after putting the local IP Address as you said in client_recv. Why didn't 0.0.0.0 work? Can you tell me what was happening when I put the local IP vs 0.0.0.0?Ouroboros

1 Answers

2
votes

TL;TR: the problem is unrelated to NAT. It is instead about less specific vs,. more specific bindings on sockets.

You have two clients on the same system:

  • client_recv.pl is bound to 0.0.0.0:
    UDP_IP = '0.0.0.0'#my ip address in the local network
  • client_send.pl is initially explicitly bound to 0.0.0.0 too:
    sock.bind(('0.0.0.0', 5000))
    But the socket will be internally re-bound to the outgoing IP of your local system in order to send the packet with the correct outgoing IP address::
    sock.sendto(MESSAGE + ' # ' + str(i), (UDP_IP, UDP_PORT))
    Note that you cannot see this new binding in netstat, it seems to be deeper in the kernel.

This means you end up with two sockets:

  • client_recv.pl has a socket bound to 0.0.0.0:5000
  • client_send.pl has a socket bound to your-local-ip:5000

If a packet arrives from the server it goes to the most specific socket, i.e. the one from client_send.pl. Thus client_recv.pl never receives the packet.

If you instead change the IP address in client_recv.pl to the IP of your local system you get two sockets, both bound to your-local-ip:5000. Since there is no most specific socket in this case the packet is actually delivered to the first one which reads it, in your case client_recv.pl.