Search code examples
pythonnat

python server-client nat traversal


I've done some reading on UDP NAT traversal, and I'm reasonably confident I understand the basics but I'm still struggling with an implementation.

My project has an globally accessible server, and clients behind nat. Its a game, with the basic join_game request send from client to server, and the server then sends out updates every interval. I've been testing at home, and forgotten that I have DMZ on my router turned on so it worked fine. I sent this to some friends to test and they cannot receive updates from the server.

Here is the current methodology, all packets are UDP:

  • Client opens a socket, and sends join request to server.
  • Server gets the request and the reply-to address of the message, and replies to the client: yes you can join, and by the way I will be sending updates to your reply-to ip/port which looks like this.
  • Client catches the reply then closes the socket, and starts a UDP threaded listener class up to listen to the reply-to port the server told us about.
  • Client then catches the server updates that get flooded over, and processes them as required. Every now and then the client opens up a new socket and sends a UDP packet with an update to the server (what keys are pressed etc).

My understanding is the reply-to address that the server receives should have the correct port for traversing the client's nat. And sending packets down there often enough will keep nat traversal rule alive.

This is not happening. The client sends the join request, and receives the server's response on that socket. But when I close the socket then start up a threaded UDP listener on the reply-to port, it doesn't catch anything. Its almost as if the traversal rule is only valid for a single response packet.

I can include code if needed, but to be honest its several layers classes and objects and it does what I described above. The code works when I turn DMZ on, but not when its off.

I will include some snippets of interest.

Here is the server's handler for the join request. client_address is passed down from the threaded handler, and is the SocketServer.BaseRequestHandler attribute, self.client_address. No parsing, just passed down.

def handle_player_join(self, message, reply_message, client_address):

        # Create player id
        player_id = create_id()

        # Add player to the connected nodes dict
        self.modules.connected_nodes[player_id] = client_address

        # Create player ship entity
        self.modules.players[player_id] = self.modules.factory.player_ship( position     = (320, 220),
                                                                            bearing      = 0,
                                                                          )

        # Set reply to ACK, and include the player id and listen port
        reply_message.body                  = Message.ACK
        reply_message.data['PLAYER_ID']     = player_id
        reply_message.data['LISTEN_PORT']   = client_address[1]

        print "Player Joined :"+str(client_address)+", ID: "+str(player_id)

        # Return reply message
        return reply_message

A friend has mentioned that maybe when I send the join request, then get the response I shouldn't close the socket. Keep that socket alive, and make that the listener. I'm not convinced closing the socket will have any effect on the nat traversal, and I don't know how to spawn a threaded udp listener that takes a pre-existing socket without rewriting the whole damn thing (which I'd rather not).

Any ideas or info required?

Cheers


Solution

  • You can do any one of two things to make your code work. They are,

    Don't close the socket from which you have sent the packets to server. When you create a socket it binds to a private IP:Port. When you send a packet to server that IP:Port will be translated to your NATs one public IP:Port. Now when you close this socket then the data from your server comes first to your NATs public IP:Port and is forwarded to your private IP:Port. But as your socket is closed so no one will receive that data. Now the server has no way to know that you have created a new socket with new private IP:Port because you never sent a packet to your server after creating this new socket. So don't close the old socket. Try to listen with this old one in a thread. Or you can send a packet to the server from the new socket letting it know your new translatedpublic IP:Port. So that server can send its data to this new public IP:Port which will in turn be forwarded to your new private IP:Port.

    Close the socket but reuse the same port. When you close your old socket and create your new socket, bind it to the port on which the old socket was bound. This will not change the NATs public IP:Port and data from your server will not be interrupted.