Orbitronics
Orbitronics

Reputation: 1

Binding UDP socket to addresses not working

I have a program that repeatedly sends UDP packets to either a broadcast address (192.168.0.255) or to a set number of IP addresses. It works well when I omit code that binds the socket to multiple addresses as I don't expect the clients to send packets back to the server however I'm keen to add binding code. After adding the binding code the program fails; I am a weak networks programmer and not a very good C++ coder so there may definitely be bad practices.

Code executes on windows 10/11 using Winsock2.

The code intends to bind to multiple esp32 modules, I will not know the IP addresses at run time.

I am using UDP because I want to broadcast a datastream to multiple nodes with minimal overhead in a setup that ignores packet loss.

I'm attempting to write a loop that executes once in the main broadcasting thread loop that scans all specified IP addresses (line 93), and saves a list of IP addresses that it bound to. Then the main broadcasting loop (line 133) sendto's to those IP addresses. The issue is I am unable to bind to any IP address and I get error: 10049 returned from a call to WSAGetLastError().

I seem to get a SOCKET_ERROR on the bind function on line 105.

I am expecting binding to succeed for at least two IP addresses that I could send packets to before i added the binding code.

I've tried binding to a single IP address however this also fails.

#include <iostream>
#include <WS2tcpip.h>
#include <WinSock2.h>
#include <windows.h>
#include <thread>
#include <atomic>
#include <string>
#include <random>
#include <conio.h>

#pragma comment(lib, "ws2_32.lib") //obj comment to link winsock to the executable 

#define USE_BROADCAST_ADDRESS       false // use broadcast address in general unless client doesn't support it
#define FREQUENCY_DELAY             1  // delay  =  1 / Freq Delay
#define UPDATE_QUADPART_TICKS       QueryPerformanceCounter(&tick); // update the high-resolution counter
#define TICKS_PER_SEC               ticksPerSecond.QuadPart // retrieve the number of ticks per second
#define SOCKET_PORT                 54000

// Define the subnet range for UDP broadcasting
std::string subnet = "192.168.0"; // Subnet address without the last octet
int startIP = 1;                   // Starting IP address in the subnet
int endIP =255;                   // Ending IP address in the subnet

// Atomic variables for safe multithreading
std::atomic<unsigned int> frequencyDelay(FREQUENCY_DELAY); 
std::atomic<bool> broadcastLoopFlag(false);    // atomic flag to kill the broadcast loopers thread
std::atomic<bool> isAltering(false);   // stop the server loop while we're changing the frequency
std::atomic<bool> breakLoop(false);    // main loop flag

int servLen = sizeof(sockaddr_in); // Define the server address structure length

/*
Clean up winsock resources
*/
void cleanup()
{
    WSACleanup();
}

/*
Random string generator, used to create 'length' long random strings
*/
std::string generateRandomString(int length)
{
    static const std::string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    static std::random_device rd;
    static std::mt19937 gen(rd());
    static std::uniform_int_distribution<> dis(0, charset.length() - 1);

    std::string randomString;
    randomString.reserve(length);

    for (int i = 0; i < length; ++i)
    {
        randomString += charset[dis(gen)];
    }

    return randomString;
}

/*
 Broadcasting happens here in seperate thread.
 It continually sends UDP packets to all IP addresses in the subnet until `whileFlag` is set to `true`.
 The frequency of the broadcasting is controlled by `frequencyDelay` (1/n).
*/
void threadLoop()
{
    std::vector<std::string> boundIPAddresses;     // Collection of successfully bound IP addresses
    /*
    Create a UDP Socket

    First Param = socket family, style and type of address  (ipv4)
    AF_INET means IPv4
    Second Param = socket type, determine kind of packet it can receive
    SOCK_DGRAM means datagrams since udp deals with datagrams
    third param = protocol, related closely to socket type than family,
    since udp protocol must be used with datagram socket but either ipv4 or 6
    */
    SOCKET serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // scope of whole thread
    if (serverSocket == INVALID_SOCKET)
    {
        std::cerr << "Can't create a socket! Quitting" << std::endl;
        closesocket(serverSocket);
        return;
    }
    sockaddr_in serverAddr; // scope of whole thread
    serverAddr.sin_family = AF_INET; // ipv4 family
    serverAddr.sin_port = htons(SOCKET_PORT); //take host byte order (big endian) and return 16 bit network byte; ip port host to network byte                                                              
    serverAddr.sin_addr.s_addr = INADDR_ANY;
                                              
                                              //bind ip addresses once at the start of this loop, only send data to the bound ip addresses
    std::cout << "              Attempting to bind IP Addresses" << std::endl;
    for (int ip = startIP; ip <= endIP; ++ip)
    {
        std::string ipAddress = subnet + "." + std::to_string(ip); // set the ip string with the final octet
        //std::string ipAddress = "127.0.0.1"; //for debugging as this should always work
        /*
        inet_pton: ip string to binary representation conversion
        AF_INET: ipv4
        2nd param: returns pointer of char array of string
        3rd param: result store in memory
        */
        inet_pton(AF_INET, ipAddress.c_str(), &(serverAddr.sin_addr));

        if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        {
            std::cout << "bind failed with " << ipAddress << "; Error: " << (int)WSAGetLastError() << std::endl;
            continue;
        }
        else
        {
            std::cout << "              Bound to: " << ipAddress << std::endl;
            boundIPAddresses.push_back(ipAddress);
        }
    }
    if (boundIPAddresses.empty())
    {
        std::cout << "No IP addresses bound to" << std::endl;
        broadcastLoopFlag = true; // Stop the broadcasting thread
        breakLoop = true; // Exit the main loop
    }

    std::string buf(300, '\0'); // Message to be sent; Initialize buf with 300 null characters
    LARGE_INTEGER ticksPerSecond;
    LARGE_INTEGER tick; // a point in time
    long long lastEnteredTick = 0;
    QueryPerformanceFrequency(&ticksPerSecond);    // get the high-resolution counter's accuracy
    Sleep(500);

    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

    // This loop continues until whileFlag is set to true
    while (!broadcastLoopFlag.load())
    {
        UPDATE_QUADPART_TICKS; // update the current tick count
        long long elapsedTime = tick.QuadPart - lastEnteredTick;
        long long delay = TICKS_PER_SEC / frequencyDelay.load();
        if (!isAltering && elapsedTime >= delay )
        {
            lastEnteredTick = tick.QuadPart; 

            std::cout << "Broadcasting" << std::endl;
            // Iterate over each IP address in the subnet

            for (const std::string& ipAddress : boundIPAddresses)
            {
                // Generate a random alphanumeric string for buf
                std::string randomString = generateRandomString(300);
                std::copy(randomString.begin(), randomString.end(), buf.begin());

                std::cout << "Sending UDP packet to: " << ipAddress << std::endl;
                sendto(serverSocket, buf.c_str(), buf.length(), 0, (const sockaddr*)&serverAddr, servLen);//send the UDP packet
            }
        }
    }
    closesocket(serverSocket);
}

/*
initializes Winsock, starts the broadcasting thread, and then enters a loop where it waits for user input to either change the broadcast frequency or exit the program.

*/
int main()
{
    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    // Initialize winsock
    WSADATA wsData; 
    WORD ver = MAKEWORD(2, 2);
    int wsOk = WSAStartup(ver, &wsData); //responsible for retrieving details of the winsock implementation that got linked into the executable
    // Check for winsock initialization failure
    if (wsOk != 0)
    {
        std::cerr << "Can't initialize winsock! Quitting program" << std::endl;
        return 1;
    }

    // use broadcast address in general unless client doesn't support it
    if (USE_BROADCAST_ADDRESS) 
    { 
        startIP = 255;
        endIP = 255;
    } 

    // Create and start the broadcasting thread
    std::thread myThread(threadLoop);//execute threadLoop function in new thread

    // Instructions for the user
    std::cout << "c: change frequency (1/n)\ne: exit program\n" << std::endl;

    // This loop waits for user commands
    while (!breakLoop)
    {
        if (_kbhit()) // function checks if a key has been pressed
        {
            char ch = _getch(); //retrieves the pressed key without requiring the Enter key
            switch (ch)
            {
            case 'c':
                isAltering = true; // exit the broadcast threads inner loop
                std::cout << "Enter your desired frequency (1/n): " << std::endl;
                unsigned int userInput; // store the user input for frequency delay
                std::cin >> userInput;
                frequencyDelay.store(userInput); // change the frequency here
                isAltering = false;
                break;
            case 'e':
                isAltering = true;
                std::cout << "Exiting." << std::endl;
                broadcastLoopFlag = true; // Stop the broadcasting thread
                breakLoop = true; // Exit the main loop
            }
        }
    }
    myThread.join();  // Wait for the broadcasting thread to finish
    // Properly clean up resources
    cleanup();

    return 0;
}

Upvotes: 0

Views: 915

Answers (1)

Ben Voigt
Ben Voigt

Reputation: 283793

I am expecting binding to succeed for at least two IP addresses that I could send packets to before i added the binding code.

You seem to be totally misunderstanding bind(). It is used to control the local address of your socket, the address packets should be addressed to in order for your socket to receive them. It also becomes the sender address for packets you send out. It has nothing to do with the remote addresses you are sending to.

bind() is expected to fail if you pass it an IP address that doesn't belong to a local network interface (network card or VPN endpoint).

A single socket also has only a single local address, so calling bind() multiple times is a recipe for failure.

Upvotes: 1

Related Questions