3

I'm trying to learn the basics of network programming by using Winsock2 API. I've had success connecting through LAN IP addresses, but I've been struggling for over a day now trying to get the client to connect to my server through its public IP.

I have already set up port forwarding on my router and have even used Wireshark to watch for client connection requests. I'm seeing the requests in Wireshark, but it never connects to the server and eventually, I get a timeout error.

I'm at a loss, I appreciate anyone who can point me in the right direction!

This is the client implementation:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")



#define DEFAULT_PORT //MY PORT
#define DEFAULT_BUFLEN 512
#define SERVER_IPV4 //"MY PUBLIC IP STRING"


int main(int argc, char **argv)
{

    //wsaData to hold Winsock dll information
    WSADATA wsaData;
    WORD wVersionRequired = MAKEWORD(2, 2);

    //Attempts to load winsock dll matching required version and fills WSADATA object
    int wsaInit = WSAStartup(wVersionRequired, &wsaData);
    if(wsaInit != 0)
    {
        printf("WSAStartup failed with error code: %d\n", wsaInit);
    }

    //If dll fails to load correct version free winsock dll resources
    if(wsaData.wHighVersion != wVersionRequired)
    {
        printf("No usable version of Winsock.dll found\n");
        WSACleanup();
        return 1;
    }
    else
    {
        printf("Winsock dll 2.2 loaded correctly\n");
    }


    /**********************Socket Code Here**********************/

    SOCKADDR_IN SockAddrIP4;
    SockAddrIP4.sin_family = AF_INET;
    SockAddrIP4.sin_addr.s_addr = inet_addr(SERVER_IPV4);
    SockAddrIP4.sin_port = htons(DEFAULT_PORT);

    /**************Create Socket****************/

    //INVALID_SOCKET used like NULL
    SOCKET ConnectSocket = INVALID_SOCKET;

    // TODO(baruch): Only supporting IP_V4
    ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if(ConnectSocket == INVALID_SOCKET)
    {
        printf("socket() error: %ld\n", WSAGetLastError());
        //clean up address info after getaddrinfo function when socket fails
        WSACleanup();
        return 1;
    }

    /*****************Connect to socket**************/

    int connectResult = connect(ConnectSocket, (SOCKADDR*)&SockAddrIP4, sizeof(SOCKADDR_IN));
    if(connectResult == SOCKET_ERROR)
    {
        printf("Connect failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        ConnectSocket = INVALID_SOCKET;
    }
    else
    {
        printf("Connected with server: %s\n", SERVER_IPV4);
    }

    if(ConnectSocket == INVALID_SOCKET)
    {
        printf("Unable to connect with server\n");
        WSACleanup();
        return 1;
    }

    /*************END of Socket CODE CLEANUP********/

    // TODO(baruch): close socket
    WSACleanup();

}

And this is the server:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")

// TODO(baruch): Only supporting ascii consider Unicode later
#undef UNICODE

#define DEFAULT_PORT //My Port
#define DEFAULT_BUFLEN 512

int main(int argc, char **argv)
{

    //wsaData to hold Winsock dll information
    WSADATA wsaData;
    WORD wVersionRequired = MAKEWORD(2, 2);


    int wsaInit = WSAStartup(wVersionRequired, &wsaData);
    if(wsaInit != 0)
    {
        printf("WSAStartup failed with error code: %d\n", wsaInit);
    }

    //If dll fails to load correct version free winsock dll resources
    if(wsaData.wHighVersion != wVersionRequired)
    {
        printf("No usable version of Winsock.dll found\n");
        WSACleanup();
        return 1;
    }
    else
    {
        printf("Winsock dll 2.2 loaded correctly\n");
    }

    /**********************Socket Code Here**********************/


    /**************Create Socket****************/
    SOCKADDR_IN SockAddrIP4;
    SockAddrIP4.sin_family = AF_INET;
    SockAddrIP4.sin_addr.s_addr = INADDR_ANY;
    SockAddrIP4.sin_port = htons(DEFAULT_PORT);

    //INVALID_SOCKET used like NULL
    SOCKET ListenSocket = INVALID_SOCKET;

    // TODO(baruch): Only supporting IP_V4
    // Socket for server to listen on for client connections
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if(ListenSocket == INVALID_SOCKET)
    {
        printf("socket() error: %ld\n", WSAGetLastError());
        //clean up address info after getaddrinfo function when socket fails
        WSACleanup();
        return 1;
    }

    /**************Bind Socket******************/
    int bindResult = bind(ListenSocket, (SOCKADDR*)&SockAddrIP4, sizeof(SOCKADDR_IN));
    if(bindResult == SOCKET_ERROR)
    {
        printf("failed to bind with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    /************Listen for Connections**********/
    int listenResult = listen(ListenSocket, SOMAXCONN);
    if(listenResult == SOCKET_ERROR)
    {
        printf("Listen failed, error: %d\n", WSAGetLastError() );
        WSACleanup();
        return 1;
    }
    else
    {
        printf("Now listening for client connections...\n");
    }
    /************Accept and Handle CLient Connections***********/
    // TODO(baruch): For testing, only allowing a single client. Eventually need to create a loop to handle all client connections.
    SOCKET ClientSocket;
    SOCKADDR_IN connectedAddress;
    int addressLength = sizeof(connectedAddress);
    ClientSocket = accept(ListenSocket, (SOCKADDR *) &connectedAddress, &addressLength);
    if(ClientSocket == SOCKET_ERROR)
    {
        printf("Accept failed, error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    else
    {
        //inet_ntoa converts ip address to binary format.
        char *clientIp = inet_ntoa(connectedAddress.sin_addr);
        // TODO(baruch): Make sure this string correctly prints address
        printf("Client connection from: %s accepted\n", clientIp);
    }

    /**********Handle inbound and outbound data**********/

    char inBuf[DEFAULT_BUFLEN];
    int dataBufLen = DEFAULT_BUFLEN;
    int inDataResult, outDataResult;

    do
    {
        inDataResult = recv(ClientSocket, inBuf, dataBufLen, 0);
        if(inDataResult > 0)
        {
            printf("Number of bytes received: %d", inDataResult);

            //Confirm to client message received
            char confirmReceipt[] = "\nMessage received!\n";
            outDataResult =
                send(ClientSocket, confirmReceipt, sizeof(confirmReceipt), 0);
            if(outDataResult == SOCKET_ERROR)
            {
                printf("Confirmation message failed with error: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            else
            {
                printf("Confirmation message sent\n");
            }
        }
        else if(inDataResult == 0)
        {
            printf("Connection closing\n");
        }
        else
        {
            printf("Data receipt failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }

    } while(inDataResult > 0);

    //Shutdown sending portion of socket, can still receive data
    int shutDownResult = shutdown(ClientSocket, SD_SEND);
    if(shutDownResult == SOCKET_ERROR)
    {
        printf("Shutdown failure, error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }


    /*************End Socket Code Clean up Winsock dll**********/

    closesocket(ClientSocket);
    WSACleanup();
}

2 Answers2

0

If you can connect via the local LAN IP Address, but not via the public IP address, it's likely one of these issues.

  1. Did you enable your program to pass through the Windows Firewall? Completely turn off the Windows Firewall (temporarily) just to make sure.

  2. If both your client and server are behind the same NAT, your NAT may not allow for client connections to connect via the public IP address. This is called NAT hairpinning. Not all NATs support this. Validate that you can connect to your server's IP address via a client outside the network of your server.

  3. When in doubt, use a simple program like netcat to test socket connectivity between PCs. Easy test is to just run nc in listen mode on your port and then to use another instance of nc to connect to it. Do an Internet search for "Netcat for Windows". If you can connect to your port via netcat, but not through your client/server code, then the issue with with your code. If you can't connect to your port via netcat, then it's a firewall or network configuration error.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • I'll give netcat a try. Thank you! –  Sep 16 '18 at 19:02
  • So I ran Nmap on my port and I'm getting this message: Starting Nmap 7.70 ( https://nmap.org ) at 2018-09-16 14:23 Central Daylight Time Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn Nmap done: 1 IP address (0 hosts up) scanned in 8.05 seconds That seems to be evidence that something is blocking the connection? –  Sep 16 '18 at 19:24
  • Did you eliminate the NAT hairpinning issue by testing from outside your NAT? – selbie Sep 16 '18 at 19:32
  • That's my next test, I don't have immediate access to an external windows pc. I just confirmed that my code still runs properly on my LAN network after my latest changes, so I'll test externally when I can. Thanks again. –  Sep 16 '18 at 19:38
  • I finally had the chance to test from an external network but am getting the same results. I'll keep trying other things. –  Sep 19 '18 at 03:42
  • So what was it? – selbie Sep 20 '18 at 01:41
  • My ISP uses Carrier-grade NAT so port forwarding had no effect. They just gave me a real public IP and everything works. –  Sep 20 '18 at 17:53
  • Yep. "carrier-grade" nat is essentially "double natted" which essentially is another form of "symmetric nat", which makes it really hard to get ports mapped. Glad you got it working. – selbie Sep 20 '18 at 18:37
0

Thanks to Selbie I was able to go through a process of elimination and determine there is something wrong with my service. I contacted my ISP, and they had to change the service type and give me a real public IP. By default, they use carrier-grade NAT which means that residential sites are assigned a private IP that is translated to a public IP by a "middlebox network address translator" somewhere in the ISP's network. Thank you!