Search code examples
cwindowsperformancewinapiport-scanning

Speedup Windows C Port Scanner


So I developed a port scanner for C on windows but I have noticed on some IP's it runs very slowly. Here's my code for it:

DWORD WINAPI connectPortW(LPVOID lpParam)
{
    HANDLE hStdout;
    PMYDATA pDataArray;

    WSADATA firstsock;
    SOCKET s;
    struct sockaddr_in sa;
    int err;

    char * openPorts = (char *)malloc(sizeof(char)*256);
    memset(&openPorts[0], 0, strlen(openPorts));

    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if(hStdout == INVALID_HANDLE_VALUE )
    {
        return 1;
    }

    pDataArray = (PMYDATA)lpParam;

    strncpy((char *)&sa,"",sizeof sa);  
    sa.sin_family = AF_INET;

    if (WSAStartup(MAKEWORD(2,0),&firstsock) != 0)
    {
        fprintf(stderr,"WSAStartup() failed"); 
        exit(1);
    }

    sa.sin_addr.s_addr = inet_addr(pDataArray->ip); 

    s = socket(AF_INET, SOCK_STREAM, 0); //make net a valid socket handle
    if(s < 0)
    {
        perror("\nSocket creation failed");  // perror function prints an error message to stderr
        exit(1);
    }

    sa.sin_port = htons(pDataArray->port);
    err = connect(s, (struct sockaddr *)&sa, sizeof sa);

    //connection not accepted
    if(err == SOCKET_ERROR)
    {
        printf("%s %-5d Winsock Error Code : %d\n", pDataArray->ip, pDataArray->port, WSAGetLastError());
        strcpy("NULL", openPorts);
        fflush(stdout);
    }
    //connection accepted
    else
    {
        printf("%s %-5d accepted            \n", pDataArray->ip, pDataArray->port);
        sprintf(openPorts, "%i,", pDataArray->port);
        if(shutdown(s, SD_BOTH ) == SOCKET_ERROR )
        {
            perror("\nshutdown");
            exit(1);   
        }
   } 
   closesocket(s); 

   fflush(stdout);

   strcpy(pDataArray->openPorts, openPorts);

   free(openPorts);

   return 0;
}

Keep in mind I already use threads and each thread calls this function for a different port (0 - 1024) on the same IP.

So how can I speed this up? I keep seeing people talking about non-blocking, would that speed it up and if so how can I implement that. Thanks!

Edit: It is taking 614 seconds (10 minutes) to scan from 0 - 1024 on one of the aforementioned 'slow' ip's

Edit 2: I started trying to use non-blocking... Am I doing this right?

ioctlsocket(s, FIONBIO, &on);
connect(s, (struct sockaddr *)&sa, sizeof sa);
FD_ZERO(&fds);
FD_SET(s, &fds);

err = select(s, &fds, &fds, &fds, &tv);

if (err != SOCKET_ERROR && err != 0)
{
    sprintf(openPorts + strlen(openPorts),"%i,", pDataArray->port);
}
closesocket(s);

Edit 3: It seems this new method is giving me inaccurate results but much much faster. I seem to be getting more open ports then compared to the results of running nmap on the same IP.


Solution

  • I see a lot of problems with your thread code:

    • it is leaking memory if a failure happens.

    • You are misusing strlen() when calling memset() on your openports variable. Just remove the memset() altogether and use calloc() or LocalAlloc(LMEM_ZEROINIT) instead when allocating openports. Or, just use the call stack instead, since the variable is small: char openPorts[256] = {0}; Or better, don't even use a local openports variable at all, simply write to pDataArray->openPorts directly when you have a result available.

    • You should not be using exit() at all. Use return instead.

    • it is not technically illegal to call WSAStartup()/WSACleanup() multiple times, since WinSock is reference counted, however it is best to call them only once at program startup/exit, not per thread. But, since you are calling WSAStartup(), you must call WSACleanup() to keep the WinSock reference count balanced.

    • What are you trying to do with strcpy("NULL", openPorts);? You are writing to read-only memory. I think you mean strcpy(openPorts, "NULL"); instead.

    • writing to pDataArray->openPorts is not thread-safe if multiple threads are sharing a single buffer (your use of , in your sprintf() string implies that may be the case). You need to synchronize access to the buffer when writing to it across multiple threads. you can use a critical section or mutex for that purpose.

    That being said, you are using a blocking socket, so connect() will block the thread until WinSock times out internally, which may take awhile on slow networks. To speed it up, switch the socket to non-blocking mode using ioctrlsocket(FIONBIO), and then use select() to implement your own timeout for connect(), eg:

    DWORD WINAPI connectPortW(LPVOID lpParam)
    {
        PMYDATA pDataArray = (PMYDATA) lpParam;
    
        HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
        if (hStdout == INVALID_HANDLE_VALUE)
            return 1;
    
        WSADATA wsa;
        int err = WSAStartup(MAKEWORD(2,0), &wsa);
        if (err != 0)
        {
            fprintf(stderr, "%s %d WSAStartup() failed, Error Code : %d\n", pDataArray->ip, pDataArray->port, err);
            return 1;
        }
    
        SOCKET s = socket(AF_INET, SOCK_STREAM, 0); //make net a valid socket handle
        if (s == INVALID_SOCKET)
        {
            fprintf(stderr, "%s %d Socket creation failed, Error Code : %d\n", pDataArray->ip, pDataArray->port, WSAGetLastError());
            WSACleanup();
            return 1;
        }
    
        u_long enabled = 1;
        if (ioctlsocket(s, FIONBIO, &enabled) == SOCKET_ERROR)
        {
            fprintf(stderr, "%s %d Socket non-blocking mode failed, Error Code : %d\n", pDataArray->ip, pDataArray->port, WSAGetLastError());
            closesocket(s);
            WSACleanup();
            return 1;
        }
    
        struct sockaddr_in sa = {0};
        sa.sin_family = AF_INET;
        sa.sin_addr.s_addr = inet_addr(pDataArray->ip); 
        sa.sin_port = htons(pDataArray->port);
    
        if (connect(s, (struct sockaddr *)&sa, sizeof sa) == SOCKET_ERROR)
        {
            err = WSAGetLastError();
            if (err != WSAEWOULDBLOCK)
            {
                fprintf(stderr, "%s %d Socket connect failed, Error Code : %d\n", pDataArray->ip, pDataArray->port, err);
                closesocket(s);
                WSACleanup();
                return 1;
            }
    
            fd_set wfd, efd;
    
            FD_ZERO(s, &wfd);
            FD_SET(s, &wfd);
    
            FD_ZERO(s, &efd);
            FD_SET(s, &efd)'
    
            timeval timeout;
            timeout.tv_sec = 5;
            timeout.tv_usec = 0;
    
            err = select(0, NULL, &wfd, &efd, &timeout);
            if (err == SOCKET_ERROR)
            {
                fprintf(stderr, "%s %d Socket select failed, Error Code : %d\n", pDataArray->ip, pDataArray->port, WSAGetLastError());
                closesocket(s);
                WSACleanup();
                return 1;
            }
    
            if (err == 0)
            {
                // connect timeout
                closesocket(s);
                WSACleanup();
                return 0;
            }
    
            if (FD_ISSET(s, &efd))
            {
                err = 0;
                getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, sizeof err);
                closesocket(s);
                WSACleanup();
    
                switch (err)
                {
                    case WSAETIMEDOUT: // connect timeout
                    case WSAECONNREFUSED: // port closed
                        return 0;
                }
    
                fprintf(stderr, "%s %d Socket connect failed, Error Code : %d\n", pDataArray->ip, pDataArray->port, err);
                return 1;
            }
        }
    
        // connected!
        printf("%s %d accepted\n", pDataArray->ip, pDataArray->port);
    
        // note, this is not thread-safe! Need to sync access to openPorts...
        sprintf(pDataArray->openPorts + strlen(pDataArray->openPorts), "%d,", pDataArray->port);
    
        closesocket(s); 
        WSACleanup();
        return 0;
    }