0
votes

I'm having trouble implementing an UDP connection because when I try it inside a LAN it works but when someone from inside a NAT tries to connect to the public server address, it fails because the packets sent from the server as response, never reach the client.

My protocol is as follows:

  1. Client A send a byte as connection request to the server.
  2. Server B creates a new socket for the client and responds a byte from there to the client port reported in the recvfrom() call. Never reaches the client.

I also tried:

Doing many calls sending a byte each for step 1.

Doing many calls sending a byte each for step 2.

Client code:

#define GPK_CONSOLE_LOG_ENABLED
#include "gpk_stdsocket.h"
#include "gpk_sync.h"

int main() {
    ::gpk::tcpipInitialize();
    sockaddr_in         sa_server           = {AF_INET};
    while(true) {
//#define MAKE_IT_WORK
#if defined MAKE_IT_WORK
        ::gpk::tcpipAddressToSockaddr({{192,168,0,2}, 9898}, sa_server);
#else
        ::gpk::tcpipAddressToSockaddr({{201,235,131,233}, 9898}, sa_server);
#endif
        ::gpk::SIPv4        addrRemote          = {};
        SOCKET              handle              = socket(AF_INET, SOCK_DGRAM, 0);
        ree_if(INVALID_SOCKET == handle, "Failed to create socket.");
        sockaddr_in         sa_client           = {AF_INET};
        gpk_necall(::bind(handle, (sockaddr *)&sa_client, sizeof(sockaddr_in)), "Failed to bind listener to address");
        char                commandToSend       = '1';
        int                 sa_length           = sizeof(sa_server);
        gpk_necall(sendto(handle, (const char*)&commandToSend, (int)sizeof(char), 0, (sockaddr *)&sa_server, sa_length), "Failed to send connect request to server.");
        {
            sockaddr_in         sa_battery          = sa_server;
            for(uint32_t j=3; j < 3; ++j) {
            for(uint32_t i=16*1024; i < 64*1024; ++i) {
                sa_battery.sin_port = htons((u_short)i);
                gpk_necall(sendto(handle, (const char*)&commandToSend, (int)sizeof(char), 0, (sockaddr *)&sa_battery, sa_length), "Failed to send connect request to server.");
                //::gpk::sleep(1);
            }}
        }
        ::gpk::tcpipAddressFromSockaddr(sa_server, addrRemote);
        info_printf("Send connect request to server: %c to %u.%u.%u.%u:%u", commandToSend, GPK_IPV4_EXPAND(addrRemote));

        char                connectAcknowledge  = 0;
        sa_server.sin_port  = 0;
        gpk_necall(recvfrom(handle, (char *)&connectAcknowledge, (int)sizeof(char), 0, (sockaddr *)&sa_server, &sa_length), "Failed to receive response from server");
        addrRemote          = {};
        ::gpk::tcpipAddressFromSockaddr(sa_server, addrRemote);
        info_printf("Received connect response from server: %c from %u.%u.%u.%u:%u.", connectAcknowledge, GPK_IPV4_EXPAND(addrRemote));
        ::gpk::sleep(1000);
        gpk_safe_closesocket(handle);
    }
    ::gpk::tcpipShutdown();
    return 0;
}

Server code:

#define GPK_CONSOLE_LOG_ENABLED
#include "gpk_stdsocket.h"
#include "gpk_sync.h"

int main() { 
    ::gpk::tcpipInitialize();
    sockaddr_in             sa_server               = {};
    ::gpk::SIPv4            addrLocal               = {{}, 9898};
    ::gpk::tcpipAddress(0, 9898, 1, ::gpk::TRANSPORT_PROTOCOL_UDP, addrLocal);
    ::gpk::tcpipAddressToSockaddr(addrLocal, sa_server);

    SOCKET                  handle                  = socket(AF_INET, SOCK_DGRAM, 0);
    ree_if(INVALID_SOCKET == handle, "Failed to create socket.");
    gpk_necall(::bind(handle, (sockaddr *)&sa_server, sizeof(sockaddr_in)), "Failed to bind listener to address");
    info_printf("Server listening on %u.%u.%u.%u:%u.", GPK_IPV4_EXPAND(addrLocal));
    while(true) {
        ::gpk::SIPv4            addrLocalClient         = addrLocal;
        addrLocalClient.Port = 0;
        SOCKET                  clientHandle            = socket(AF_INET, SOCK_DGRAM, 0);

        sockaddr_in             sa_server_client        = {AF_INET};
        ::gpk::tcpipAddressToSockaddr(addrLocalClient, sa_server_client);
        gpk_necall(::bind(clientHandle, (sockaddr *)&sa_server_client, sizeof(sockaddr_in)), "Failed to bind listener to address");

        sockaddr_in             sa_client               = {AF_INET};
        int                     client_length           = sizeof(sa_client);
        char                    connectReceived         = 0;
        gpk_necall(::recvfrom(handle, (char*)&connectReceived, (int)sizeof(char), 0, (sockaddr*)&sa_client, &client_length), "Failed to receive connect request.");
        ::gpk::SIPv4            addrRemote;
        ::gpk::tcpipAddressFromSockaddr(sa_client, addrRemote);
        info_printf("Received connect request: %c from %u.%u.%u.%u:%u.", connectReceived, GPK_IPV4_EXPAND(addrRemote));

        char                    commandToSend           = '2';
        //::gpk::tcpipAddressFromSockaddr(sa_server, addrLocal);
        ::gpk::tcpipAddress(clientHandle, addrLocal);
        info_printf("Sending connect response %c from %u.%u.%u.%u:%u to %u.%u.%u.%u:%u.", commandToSend, GPK_IPV4_EXPAND(addrLocal), GPK_IPV4_EXPAND(addrRemote));
        ::gpk::sleep(10);
        ree_if(INVALID_SOCKET == clientHandle, "Failed to create socket.");
        for(uint32_t i=16*1024; i < 65535; ++i)
            gpk_necall(::sendto(clientHandle, (const char*)&commandToSend, (int)sizeof(char), 0, (sockaddr*)&sa_client, sizeof(sockaddr_in)), "Failed to respond.");
        info_printf("Sent connect response %c from %u.%u.%u.%u:%u to %u.%u.%u.%u:%u.", commandToSend, GPK_IPV4_EXPAND(addrLocal), GPK_IPV4_EXPAND(addrRemote));
        if(handle != clientHandle)
            gpk_safe_closesocket(clientHandle);
    }
    ::gpk::tcpipShutdown();
    return 0; 
}

Note: I left the sample udp_server and udp_client projects at https://github.com/asm128/gpk in case you want a working example that compiles the working and broken cases conditionally by uncommenting //#define MAKE_IT_WORK and by placing your IP address in place of mine.

1
Add the code you tried to the question. Without all the parts that aren't important.user253751
It seems you figured out the problem yourself. The server needs to send responses using the same socket that requests were received on. As I mentioned before, for UDP to work through a NAT the port numbers have to match in both directions.dbush
Also, fixing the names isn't a matter of making things easier to understand, it's a matter of accuracy. Computers do exactly what you tell them, so if that was your real code it may have explained the problem. Code posted here should be the exact code you run that exhibits your problem, otherwise you're wasting everyone's time. And it's not insulting to suggest that the problem may be in your code based on the expertise of the person offering assistance.dbush
@PabloAriel Except that you originally posted that you tried using both a new socket and the existing socket and both approaches failed.dbush
UDP is connectionless, so you have to use a single socket for all communications so that the server port number remains the same. It's not the same as TCP. A TCP socket is defined by both a local IP/port and a remote IP/port, while a UDP socket is defined by only a local IP/port.dbush

1 Answers

3
votes

Your responses are not getting back to the client because you're using a separate socket to send responses back. This socket has a different local port number than the socket that received the packet from the client, so to the NAT it appears to be coming from a different source and is therefore not forwarded.

When a UDP datagram exits a NAT, the NAT keeps track of the destination IP and port along with the local IP and port used by the NAT and matches that with the original source IP and port on the inside network. For an incoming packet to pass back through to the original source, the source IP and port of the incoming must match the destination IP and port of the prior outgoing packet, and the destination IP and port of the incoming packet must match the IP and port of the NAT for the outgoing packet. If that condition is met, the NAT forward the packet to the original source IP and port. This is referred to as UDP hole punching

Let's illustrate this with an example. Suppose you have the following hosts:

  • server (outside of NAT): IP 192.168.0.10
  • NAT: internal IP 192.168.0.1, external IP 10.0.0.1
  • client (inside of NAT): IP 10.0.0.2

Your server creates a socket bound to point 9898 and waits. Your client then creates a socket bound to port 0, meaning a random port is chosen. Let's assume it's port 10000. The client then sends a UDP packet to 192.168.0.10:9898. So the packet has:

  • Source IP: 10.0.0.2
  • Destination IP: 192.168.0.10
  • Source port: 10000
  • Destination port: 9898

The packet then passes through the NAT, which adjusts the source IP and port so responses can be sent back to the client. It chooses port 15000. So now the packet looks like this:

  • Source IP: 192.168.0.1
  • Destination IP: 192.168.0.10
  • Source port: 15000
  • Destination port: 9898

If the NAT later sees a packet coming from the outside network with same IP/port pairs above but with source/destination reversed, it will send it to 10.0.0.2:10000.

The server then receives this packet. But now you create a new socket on the server and bind it to port 0, so a random port is chosen, let's say 12000. The server then uses this socket to send the response back where it came. So the response packet looks like this:

  • Source IP: 192.168.0.10
  • Destination IP: 192.168.0.1
  • Source port: 12000
  • Destination port: 15000

The NAT then receives this packet, and needs to decide whether to forward it to an internal host. Had the source port been 9898 it would change the destination IP/port to 10.0.0.2:10000 and send it there. But it doesn't match, so the NAT drops the packet.

The server needs to use the same socket that received the packet from the client to send the response back. If it does the packet will look like this:

  • Source IP: 192.168.0.10
  • Destination IP: 192.168.0.1
  • Source port: 9898
  • Destination port: 15000

And the NAT will forward it to the client because it matches the packet that went out but with the source/destination swapped.

With regard to the server handling requests from multiple clients, it needs to keep track of where the request came from and have some sort of mechanism to keep state on each client to determine the appropriate response to send.