c++socketsnetwork-programmingloopback

Sending/Receiving UDP (loopback) Packets in C++ not working


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;
};

Solution

  • 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:

    closesocket( socket );
    

    Should be handle getting passed to closesocket.

    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);
    
        ...
    
                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.