1

I am trying to follow this tutorial on how to send UDP packages in C++. Unfortunately I cannot get it to work.

I tracked loopback traffic with Wireshark on port 30001, but it did not display any packets, so it seems like the sending is already failing.

I added my executable to the firewall to ensure it's not getting blocked, and ran the program in two separate terminal windows using sudo ./main receiver or sender (Also without sudo). I am on Mac OS, which is why I have not implemented the Windows-specific code yet.

I would really appreciate some help, since I am a bit stuck, and I am quite a beginner in networking on this level.

Here is my code:

Main.cpp

#include <iostream>
#include "Socket.h"
#include "Address.h" 

int main(int argc, char* argv[])
{
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " [sender|receiver]" << std::endl;
        return 1;
    }
    
    const std::string mode = argv[1];

    if (mode == "sender") 
    {
        int port = 30001;
        Address sendAddress = Address(127, 0, 0, 1, 30000);
        Socket socket;
        if(!socket.Open(port))
        {
            std::cout << "Failed to open socket!" << std::endl;
            return 1;
        }

        const char data[] = "Hello World!";
        if(!socket.Send(sendAddress, data, sizeof(data))
        {
            std::cout << "Failed to send message!" << std::endl;
        }
    } 
    else if (mode == "receiver") 
    {       
        Socket socket;
        int port = 30000;
        if(!socket.Open(port))
        {
            std::cout << "Failed to open socket!" << std::endl;
            return 1;
        }

        while(true)
        {
            Address sender;
            unsigned char buffer[512];
            int bytes_read = socket.Receive(sender, buffer, sizeof(buffer));
            if(!bytes_read){
                break;
            }

            if(bytes_read > 0)
            {
                std::cout << "Received " << bytes_read << " bytes" << std::endl;
                std::cout << buffer << std::endl;
            }
            else
            {
                std::cout << "." << std::endl;
            }
        }
    } 
    else 
    {
        std::cout << "Invalid mode! Use 'sender' or 'receiver'." << std::endl;
        return 1;
    }
    
    std::cout << "Program closing! Good bye!" << std::endl;    
    return 0; 
}

Socket.cpp

#define PLATFORM_WINDOWS 1
#define PLATFORM_MAC     2
#define PLATFORM_UNIX    3

#if defined(_WIN32)
#define PLATFORM PLATFORM_WINDOWS
#elif defined(__APPLE__)
#define PLATFORM PLATFORM_MAC
#else
#define PLATFORM PLATFORM_UNIX
#endif

#if PLATFORM == PLATFORM_WINDOWS
#include <winsock2.h>

#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#include "Socket.h"
#include "Address.h"
#include <iostream>

Socket::Socket()
{

}

Socket::~Socket()
{
    Close();
}

bool Socket::Open(unsigned short port)
{
    handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (handle <= 0){
        std::cout <<("Failed to create socket")<<std::endl;
        return false;
    }

    sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons((unsigned short) port);

    if(bind(handle, (const sockaddr*) &address, sizeof(sockaddr_in)) < 0)
    {
        std::cout <<("Failed to bind socket")<<std::endl;
        return false;
    }

#if PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
    int nonBlocking = 1;
    if ( fcntl( handle, F_SETFL, O_NONBLOCK, nonBlocking ) == -1 )
    {
        printf( "failed to set non-blocking\n" );
        return false;
    }
#elif PLATFORM == PLATFORM_WINDOWS
    DWORD nonBlocking = 1;
    if ( ioctlsocket( handle, FIONBIO, &nonBlocking ) != 0 )
    {
        printf( "failed to set non-blocking\n" );
        return false;
    }
#endif

    return true;
}

void Socket::Close()
{
    #if PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
    close( handle );
    #elif PLATFORM == PLATFORM_WINDOWS
    closesocket( socket );
    #endif
}

bool Socket::Send(const Address& destination, const void* packet_data, int packet_size)
{
    int sent_bytes = sendto( 
                        handle, 
                        (const char*)packet_data, 
                        packet_size,
                        0, 
                        (sockaddr*)&destination, 
                        sizeof(sockaddr_in));

    if(sent_bytes != packet_size )
    {
        printf( "failed to send packet\n" );
        return false;
    }

    std::cout << "Sent " << sent_bytes << " bytes" << std::endl;

    return true;
}

int Socket::Receive(Address& sender, void * data, int size)
{
#if PLATFORM == PLATFORM_WINDOWS    
    typedef int socklen_t;
#endif

    unsigned char packet_data[size];
    unsigned int max_packet_size = sizeof( packet_data );

    sockaddr_in from;
    socklen_t fromLength = sizeof( from );

    int bytes = recvfrom(handle, 
                        (char*)packet_data, 
                        max_packet_size,
                        0, 
                        (sockaddr*)&from, 
                        &fromLength );

    if ( bytes <= 0 )
        return -1;

    unsigned int from_address = ntohl( from.sin_addr.s_addr );
    unsigned int from_port =  ntohs( from.sin_port );
    sender = Address( from_address, from_port );
    return bytes;
}

Address.cpp

#define PLATFORM_WINDOWS 1
#define PLATFORM_MAC     2
#define PLATFORM_UNIX    3

#if defined(_WIN32)
#define PLATFORM PLATFORM_WINDOWS
#elif defined(__APPLE__)
#define PLATFORM PLATFORM_MAC
#else
#define PLATFORM PLATFORM_UNIX
#endif

#if PLATFORM == PLATFORM_WINDOWS
#include <winsock2.h>

#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#endif

#include "Address.h"

Address::Address()
{
}

Address::Address(unsigned char a, unsigned char b, unsigned char c, unsigned char d, unsigned short port)
{
    unsigned int address = ( a << 24 ) | 
                           ( b << 16 ) | 
                           ( c << 8  ) | 
                             d;

    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl( address );
    addr.sin_port = htons( port );
    Address::port = port;
    Address::address = address;
}

Address::Address(unsigned int address, unsigned short port)
{
    Address::port = port;
    Address::address = address;
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl( address );
    addr.sin_port = htons( port );
}

unsigned int Address::GetAddress() const
{
    return address; 
}

unsigned char Address::GetA() const
{
    return ( GetAddress() >> 24 ) & 0xFF;
}

unsigned char Address::GetB() const
{
    return ( GetAddress() >> 16 ) & 0xFF;
}

unsigned char Address::GetC() const
{
    return ( GetAddress() >> 8 ) & 0xFF;
}

unsigned char Address::GetD() const
{
    return GetAddress() & 0xFF;
}

unsigned short Address::GetPort() const
{
    return port;
}

As requested here are my header files as well: Socket.h

#pragma once

class Socket
{
public:
    Socket();
    ~Socket();

    bool Open(unsigned short port);
    void Close();
    bool IsOpen() const;
    bool Send(const class Address& destination , const void * packet_data, int packet_size);
    int Receive(class Address& sender, void * data, int size);

private:
    int handle;
};

Address.h

#pragma once

class Address
{
public:

    Address();

    Address(unsigned char a, 
            unsigned char b, 
            unsigned char c, 
            unsigned char d, 
            unsigned short port );

    Address(unsigned int address, unsigned short port );

    unsigned int GetAddress() const;

    unsigned char GetA() const;
    unsigned char GetB() const;
    unsigned char GetC() const;
    unsigned char GetD() const;

    unsigned short GetPort() const;

private:

    unsigned int address;
    unsigned short port;
};
  • 1
    Can you publish your Socket.h and Address.h headers too? – selbie Jul 21 '23 at 21:13
  • Are you on Windows by change. If you're on Windows, you need to call WSAStartup.... https://www.binarytides.com/winsock-socket-programming-tutorial/ – selbie Jul 21 '23 at 21:14
  • Unrelated. But your code in `main` that prints out `buffer` after Receive returns without validating its null terminated is a security hole... – selbie Jul 21 '23 at 21:17
  • What firewall is it? (I was unaware that macOS had one) – Paul Sanders Jul 21 '23 at 21:28
  • You'll be glad to hear you don't need anyone's help to figure this out, just a tool you already have: your debugger! This is exactly what a debugger is for. It [runs your program, one line at a time, and shows you what's happening](https://stackoverflow.com/questions/25385173/), this is something that's every C++ developer must know how to do. With your debugger's help you'll able to quickly find all problems in this and all future programs you write, without having to ask anyone for help. Have you tried using your debugger, already? If not, why not? What did your debugger show you? – Sam Varshavchik Jul 21 '23 at 21:38
  • 1
    @SamVarshavchik - normally your comment is great for those new to SO who have failed to demonstrate a reasonable effort at debugging their own code. But the OP has provided a fairly detailed program with a substantial number of print statements that would show errors as the code is being run. Socket code is hard and new developers often find they can't get their client/server program to hook up even though no errors are happening at an API level. – selbie Jul 21 '23 at 22:18
  • 1
    Yes, I agree, @selbie, that this is a very detailed program. But I don't see any debugging analysis in the question, in any detail. What do you read here beyond "here's my code, it doesn't work, help"? – Sam Varshavchik Jul 21 '23 at 22:20
  • Just have to make this note: UDP is not a reliable protocol - there's no guarantee of delivery. – Jesper Juhl Jul 21 '23 at 23:20
  • @JesperJuhl - OP's code is on localhost. It should be highly reliable for simple packet pings. – selbie Jul 21 '23 at 23:24
  • @selbie of course! I updated my original post with the header file code. – Simsalabimson Jul 22 '23 at 08:20
  • @SamVarshavchik fair point, I did not go through the program line by line with a debugger. I did do that now, but it was not very insightful. The flow of the program executes as expected, as was also indicated by the console logs. Maybe you could specify, what further information you would like me to provide? – Simsalabimson Jul 22 '23 at 08:36

1 Answers1

1

Here's the root cause of your issues. This block of code:

bool Socket::Send(const Address& destination, const void* packet_data, int packet_size)
{
    int sent_bytes = sendto( 
                        handle, 
                        (const char*)packet_data, 
                        packet_size,
                        0, 
                        (sockaddr*)&destination, 
                        sizeof(sockaddr_in));

Is casting the custom Address struct, which is not a sockaddr into a sockaddr*.

Change Address to have a private member:

sockaddr_in addr;

And a public method:

sockaddr_in GetSocketAddress() const {return addr;}

Then correctly initialize it in your constructor. It looks like you are intializing a local addr variable. I think you meant for this to be a member variable.

Address::Address(unsigned char a, unsigned char b, unsigned char c, unsigned char d, unsigned short port)
{
    unsigned int address = (a << 24) |
        (b << 16) |
        (c << 8) |
        d;

    addr = {};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(address);
    addr.sin_port = htons(port);

    Address::port = port;
    Address::address = address;
}

Then when you send:

bool Socket::Send(const Address& destination, const void* packet_data, int packet_size)
{

    sockaddr_in addr = destination.GetSocketAddress();

    int sent_bytes = sendto(
        handle,
        (const char*)packet_data,
        packet_size,
        0,
        (sockaddr*)&addr,
        sizeof(sockaddr_in));

And your program will successfully send and receive at this point.

Other minor issues:

  • I had to disable non-blocking mode to get your code to work. Otherwise, your receive mode would just print out an endless stream of periods. Disable non-blocking mode if you aren't familiar with sockets.

  • Your Socket::Close call has a compile error on windows:

closesocket( socket );

Should be handle getting passed to closesocket.

  • Your code will print "Received 13 bytes", but won't print out the expected data. That's because Socket::Receive isn't copying the data over from the local buffer to the buffer passed in.

Simplify it to:

int Socket::Receive(Address& sender, void* data, int size)
{
#if PLATFORM == PLATFORM_WINDOWS    
    typedef int socklen_t;
#endif

    sockaddr_in from = {};
    socklen_t fromLength = sizeof(from);

    int bytes = recvfrom(handle,
        (char*)data,
        size,
        0,
        (sockaddr*)&from,
        &fromLength);

    ...
  • There's all kinds of security holes with not enforcing null termination on the received string before printing it. Change your receive loop to be this:
            Address sender;
            const int buffer_length = 512;
            unsigned char buffer[buffer_length + 1] = {}; // zero init, +1 for null char
            int bytes_read = socket.Receive(sender, buffer, buffer_length);
            if (!bytes_read) {
                break;
            }

            if (bytes_read > 0)
            {
                buffer[bytes_read] = '\0'; // secure that buffer before printing

                std::cout << "Received " << bytes_read << " bytes" << std::endl;
                std::cout << buffer << std::endl;
            }

To address the debugability of your code, if you captured errno or WSAGetLastError after each unsuccessful socket API call:

    int result = sendto(...);
    int error_code = (result >= 0) ? 0 : errno; // unix
    int error_code = (result >= 0) ? 0 : WSAGetLastError(); // windows

Then you could have printed those out and it would have mapped to a bad address format error.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • Well spotted, I missed that. – Paul Sanders Jul 22 '23 at 14:03
  • Hi @selbie! Thank you so much for your very detailed response! I will mark the Question as resolved. Thank you also for your patience, and extra information/tips provided. As you undoubtedly noticed, I am new to low level networking, so this really helps me a lot and I appreciate it! I will take some time to read up on the security issues, and other info you provided. Have a great Sunday! – Simsalabimson Jul 23 '23 at 14:08