0

So, I am on Windows 10, and using latest MINGW64 from MSYS2:

$ uname -a
MINGW64_NT-10.0-19043 DESKTOP-XXXXXXX 3.2.0-340.x86_64 2021-08-02 16:30 UTC x86_64 Msys

I have experiencing something strange with Winsock bind, which I can now reconstruct on a minimal working example, which is a basic server code from Winsock Server And Client Example: "getaddrinfo" was not declared in this scope , that I saved as test.cpp (EDIT: code is now with printout, EDIT2: and with input argument):


#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
//~ #define DEFAULT_PORT "27015"
#define DEFAULT_PORT "9010"

void print_getaddrinfo_response(struct addrinfo *result);

int __cdecl main(int argc, char **argv)
{
    WSADATA wsaData;
    int iResult;

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;
    char defaultport[8];

    int iSendResult;
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // here, argc==1 for no arguments
    if (argc==2) {
      snprintf( defaultport, 8, "%s", argv[1] );
    } else {
      snprintf( defaultport, 8, "%s", DEFAULT_PORT );
    }
    printf("Listening on port: %s ...", defaultport);
    // Resolve the server address and port
    iResult = getaddrinfo(NULL, defaultport, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    //print_getaddrinfo_response(result);
    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    if (argc==2) { // exit immediately
      printf(" exiting\n");
      closesocket(ListenSocket);
      WSACleanup();
      return 0;
    }

    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // No longer need server socket
    closesocket(ListenSocket);

    // Receive until the peer shuts down the connection
    do {

        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

        // Echo the buffer back to the sender
            iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed with error: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            printf("Bytes sent: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("Connection closing...\n");
        else  {
            printf("recv failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }

    } while (iResult > 0);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

    // cleanup
    closesocket(ClientSocket);
    WSACleanup();

    return 0;
}

void print_getaddrinfo_response(struct addrinfo *result) {
  // from https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-addrinfoa
  INT iRetval;
  int i = 1;
  struct addrinfo *ptr = NULL;
  struct sockaddr_in  *sockaddr_ipv4;
  LPSOCKADDR sockaddr_ip;
  char ipstringbuffer[46];
  DWORD ipbufferlength = 46;

  // Retrieve each address and print out the hex bytes
  for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {

    printf("getaddrinfo response %d\n", i++);
    printf("\tFlags: 0x%x\n", ptr->ai_flags);
    printf("\tFamily: ");
    switch (ptr->ai_family) {
      case AF_UNSPEC:
        printf("Unspecified\n");
        break;
      case AF_INET:
        printf("AF_INET (IPv4)\n");
        sockaddr_ipv4 = (struct sockaddr_in *) ptr->ai_addr;
        printf("\tIPv4 address %s\n",
          inet_ntoa(sockaddr_ipv4->sin_addr) );
        break;
      case AF_INET6:
        printf("AF_INET6 (IPv6)\n");
        // the InetNtop function is available on Windows Vista and later
        // sockaddr_ipv6 = (struct sockaddr_in6 *) ptr->ai_addr;
        // printf("\tIPv6 address %s\n",
        //  InetNtop(AF_INET6, &sockaddr_ipv6->sin6_addr, ipstringbuffer, 46) );

        // We use WSAAddressToString since it is supported on Windows XP and later
        sockaddr_ip = (LPSOCKADDR) ptr->ai_addr;
        // The buffer length is changed by each call to WSAAddresstoString
        // So we need to set it for each iteration through the loop for safety
        ipbufferlength = 46;
        iRetval = WSAAddressToString(sockaddr_ip, (DWORD) ptr->ai_addrlen, NULL,
          ipstringbuffer, &ipbufferlength );
        if (iRetval)
          printf("WSAAddressToString failed with %u\n", WSAGetLastError() );
        else
          printf("\tIPv6 address %s\n", ipstringbuffer);
        break;
      case AF_NETBIOS:
        printf("AF_NETBIOS (NetBIOS)\n");
        break;
      default:
        printf("Other %ld\n", ptr->ai_family);
        break;
    }
    printf("\tSocket type: ");
    switch (ptr->ai_socktype) {
      case 0:
        printf("Unspecified\n");
        break;
      case SOCK_STREAM:
        printf("SOCK_STREAM (stream)\n");
        break;
      case SOCK_DGRAM:
        printf("SOCK_DGRAM (datagram) \n");
        break;
      case SOCK_RAW:
        printf("SOCK_RAW (raw) \n");
        break;
      case SOCK_RDM:
        printf("SOCK_RDM (reliable message datagram)\n");
        break;
      case SOCK_SEQPACKET:
        printf("SOCK_SEQPACKET (pseudo-stream packet)\n");
        break;
      default:
        printf("Other %ld\n", ptr->ai_socktype);
        break;
    }
    printf("\tProtocol: ");
    switch (ptr->ai_protocol) {
      case 0:
        printf("Unspecified\n");
        break;
      case IPPROTO_TCP:
        printf("IPPROTO_TCP (TCP)\n");
        break;
      case IPPROTO_UDP:
        printf("IPPROTO_UDP (UDP) \n");
        break;
      default:
        printf("Other %ld\n", ptr->ai_protocol);
        break;
    }
    printf("\tLength of this sockaddr: %d\n", ptr->ai_addrlen);
    printf("\tCanonical name: %s\n", ptr->ai_canonname);
  }
}

This I compile in MINGW64:

$ g++ test.cpp -g -o test.exe -lws2_32

... and as far as building goes, it compiles without a problem. But when running:

  • When you have the code as in the original linked post, with #define DEFAULT_PORT "27015", then there is no problem, and code works - I'm running it from cmd.exe:
D:\>test.exe
getaddrinfo response 1
        Flags: 0x0
        Family: AF_INET (IPv4)
        IPv4 address 0.0.0.0
        Socket type: SOCK_STREAM (stream)
        Protocol: IPPROTO_TCP (TCP)
        Length of this sockaddr: 16
        Canonical name: (null)
Bytes received: 7
Bytes sent: 7
Connection closing...

... and the above happens in response to triggering it with telnet (which I call from the MINGW64 bash shell):

$ telnet 127.0.0.1 27015
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello
hello
↔
telnet> q
Connection closed.
  • When you have the code as in this post, that is, with #define DEFAULT_PORT "9010", it compiles fine; but then when I try to run it,it exits immediately:
D:\>test.exe
getaddrinfo response 1
        Flags: 0x0
        Family: AF_INET (IPv4)
        IPv4 address 0.0.0.0
        Socket type: SOCK_STREAM (stream)
        Protocol: IPPROTO_TCP (TCP)
        Length of this sockaddr: 16
        Canonical name: (null)
bind failed with error: 10013

Now, Socket error 10013 is a message which implies that a port is blocked and/or unreachable - which I guess means, port 9010 is blocked somehow?!

But, so far, I cannot confirm that this port is blocked in any way:

Querying the firewall (in administrative command prompt) on the test.exe program gives me:

D:\>netsh firewall show config | findstr test
Enable   Inbound              test / D:\test.exe

... which I guess means, it is allowed to have incoming connections?

D:\>netsh firewall show config | findstr 9010

D:\>

The above returns nothing, so port 9010 seems to be not explicitly mentioned in firewall.

And querying for open listening port 9010 (as in, if there is a hanging process in the background, preventing test.exe to start up), also returns nothing:

D:\>netstat -a -n | findstr 9010

D:\>

So, how in the world can I debug, and find out/confirm, why is port 9010 in this application inaccessible - but port 27015 works fine ?!


EDIT: I've added printouts in response to comment:

The getaddrinfo function can return a list of results

... and here, it seems, it returns only one - for address 0.0.0.0

Also, I agree with:

you are using a port that someone doesn't want you using.

... and, in essence, I'd like to find out what is that someone that doesn't want me using port 9010 - especially since when I try the above commands, I do not get any message, that anything is using port 9010.


EDIT2: The code can now accept the port via input argument, and if that is the case, it exits immediately (so, all the notes about recompiling and calling, still work).

That means, now I can call a loop like this:

$ for i in $(seq 8080 11000); do ./test.exe $i; done
Listening on port: 8080 ... exiting
Listening on port: 8081 ... exiting
Listening on port: 8082 ... exiting
...
Listening on port: 8818 ... exiting
Listening on port: 8819 ... exiting
Listening on port: 8820 ...bind failed with error: 10013
Listening on port: 8821 ...bind failed with error: 10013
...

Using this technique, I have so far found, that this program cannot bind to ports 8820:9519 [diff 700], 9676:9875 [diff 200] ... and probably others.

The question is: why exactly these ranges fail and not others, and how can I confirm that using any Windows application (GUI or command line?)

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • 1
    The `getaddrinfo` function can return a *list* of results, and most examples showing it will use a loop calling `socket` and `bind` until both succeeds. Have you tried that? Also, it might not be the port that's not accessible, it might be the interface that you're trying to bind to. What is the interface (IP-address) that you're attempting to bind to, have you checked that? – Some programmer dude Sep 29 '21 at 12:59
  • Thanks @Someprogrammerdude - great suggestions all, will try them and try to post back with results. – sdbbs Sep 29 '21 at 12:59
  • 2
    Per [Windows Sockets Error Codes](https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2): "*WSAEACCES 10013 Permission denied... **Another possible reason for the WSAEACCES error is that when the bind function is called (on Windows NT 4.0 with SP4 and later), another application, service, or kernel mode driver is bound to the same address with exclusive access.** Such exclusive access is a new feature of Windows NT 4.0 with SP4 and later, and is implemented by using the SO_EXCLUSIVEADDRUSE option.*" IOW, you are using a port that someone doesn't want you using. – Remy Lebeau Sep 29 '21 at 18:15
  • Thanks @RemyLebeau - post edited; I agree fully something doesn't want me using port 9010. but I have trouble confirming what is this something - `netstat`, for instance, does not show any process listening on port 9010. – sdbbs Sep 30 '21 at 07:56
  • Edited post, @Someprogrammerdude - seems I only get one result in the list from `getaddrinfor`; the IP address is 0.0.0.0, however, that is the same IP address/interface being used also when I use port 27015 for listening, and then all works fine. – sdbbs Sep 30 '21 at 07:58
  • 1
    `0.0.0.0` is the "all" or "any" interface (`INADDR_ANY`). So it does indeed seem like @RemyLebeau is correct. – Some programmer dude Sep 30 '21 at 09:03
  • Thanks @Someprogrammerdude - I agree with "*you are using a port that someone doesn't want you using*", the question is *what* doesn't want me using these ports and *why*, and *how* do I confirm that - there are ranges I cannot use (see my latest edit), but I cannot tell *why*. – sdbbs Sep 30 '21 at 09:22

1 Answers1

1

Well, finally I found a way, to have an independent check, that confirms the behavior I observe in the question.

In review, I've concluded the program in the Q, cannot bind and listen to at least port ranges 8820:9519 and 9676:9875 - but it can bind to ports for listening, outside of these ranges.

So first, I found: Troubleshoot port exhaustion issues - Windows Client Management | Microsoft Docs:

You can view the dynamic port range on a computer by using the following netsh commands:

netsh int ipv4 show dynamicport tcp
netsh int ipv4 show dynamicport udp
netsh int ipv6 show dynamicport tcp
netsh int ipv6 show dynamicport udp

Now, this in itself does not help - "dynamic ports" are the "ephemeral ports", that is used for only a short period of time for the duration of a communication session. So, now applicable to this problem.

Then, after a lot of misses, finally I hit the following page: Error 10013 when you bind excluded port again - Windows Server | Microsoft Docs:

Assume that you exclude a port by running the following command on a computer that is running Windows Server 2012 R2, Windows Server 2012, or Windows Server 2008 R2:

netsh int ipv4 add excludedportrange protocol = tcp startport = Integer numberofports = 1

Great - except I do not want to exclude ports, I want to show excluded ports - so taking in the syntax from the previous post, I tried this in administrator command prompt (cmd.exe):

D:\>netsh int ipv4 show excludedportrange protocol=tcp

Protocol tcp Port Exclusion Ranges

Start Port    End Port
----------    --------
      1074        1173
      1174        1273
      1348        1447
      1448        1547
      1548        1647
      1648        1747
      1748        1847
      1848        1947
      1948        2047
      2048        2147
      2148        2247
      5357        5357
      8820        8919  ## 8820:9519
      8920        9019  ## 8820:9519
      9020        9119  ## 8820:9519
      9120        9219  ## 8820:9519
      9220        9319  ## 8820:9519
      9320        9419  ## 8820:9519
      9420        9519  ## 8820:9519
      9676        9775  ## 2) 9676:9875
      9776        9875  ## 2) 9676:9875
      9984       10083
     10084       10183
     10184       10283
     10284       10383
     10384       10483
     10584       10683
     10684       10783
     10784       10883
     10884       10983
     10984       11083
     11084       11183
     11184       11283
     50000       50059     *

* - Administered port exclusions.

You can see where I've marked the "banned" port ranges that I've obtained by running the program in the OP, matched to the ranges output by excludedportrange, with the ## marking.

Well, finally! Questions now abound:

  • Why does excludedportrange show what is otherwise a single uninterrupted range, as multiple (7 or 2) contiguous ranges ?!
  • Why is excludedportrange not visible, or configurable, in Windows Firewall (at least I couldn't find it there so far)?

Well, at least I know now why do I observe the behavior that I do; what a relief...


EDIT: and the solution for this was here Huge amount of ports are being reserved · Issue #5306 · microsoft/WSL; also noted on SO here: Cannot bind to some ports due to permission denied

What worked for me:


D:\src\ola_mingw64_install>net stop winnat

The Windows NAT Driver service was stopped successfully.


D:\>net start winnat

The Windows NAT Driver service was started successfully.


D:\>netsh int ipv4 show excludedportrange protocol=tcp store=active

Protocol tcp Port Exclusion Ranges

Start Port    End Port
----------    --------
      5357        5357
     50000       50059     *

* - Administered port exclusions.

D:\>test.exe
Listening on port: 9010 ...Bytes received: 7
Bytes sent: 7
Connection closing...

Well, that was a shitty experience! Imagine, there was a time, when I thought computers and programming would make things easier, lol :)

sdbbs
  • 4,270
  • 5
  • 32
  • 87