# Simple C++ UDP Listener for Debugging

**URL:** <https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907>\
**Category:** Development & Maintenance\
**Created:** [June 10, 2023, 10:10pm UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907 "2023-06-10T22:10:37Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![erik.rainey](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/erik.rainey/32/20_2.png) [@erik.rainey](https://forum.opencyphal.org/u/erik.rainey)\
**Post date:** [June 10, 2023, 10:10pm UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/1 "2023-06-10T22:10:37Z")

</div>

I asked ChatGPT to write a simple C++ socket program to listen and and it didn’t work at first but after a few pointed questions and some scolding 😉 I was able to receive the Heartbeat in the c++ program.

```plaintext
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <unistd.h>

void printBufferAsHex(const char* buffer, size_t bufferSize) {
    size_t counter = 0;
    size_t const width = 32;
    size_t const width_2 = width / 2;
    for (size_t i = 0; i < bufferSize; ++i) {
        bool spacer = (counter > 0) and ((counter % width_2) == 0);
        bool newline = (counter > 0) and ((counter % width) == 0);
        printf("%s%s %02hhx", (spacer ? " " : ""), (newline ? "\r\n" : ""), buffer[i]);
        counter++;
    }

    // If the last line didn't reach width bytes, add a newline
    if (counter != 0) {
        printf("\r\n");
    }
}

int main(int argc, char* argv[]) {
    if (argc < 4) {
        std::cerr << "Usage: " << argv[0] << " <interface_address> <multicast_address> <port>" << std::endl;
        return 1;
    }

    const char* interfaceAddress = argv[1];
    const char* multicastAddress = argv[2];
    const int port = std::stoi(argv[3]);

    // Create a socket
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        std::cerr << "Failed to create socket." << std::endl;
        return 1;
    }

    // Set SO_REUSEPORT option
    int reuseAddr = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)) == -1) {
        std::cerr << "Failed to set SO_REUSEADDR option." << std::endl;
        close(sock);
        return 1;
    }

    // Set SO_REUSEPORT option
    int reusePort = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &reusePort, sizeof(reusePort)) == -1) {
        std::cerr << "Failed to set SO_REUSEPORT option." << std::endl;
        close(sock);
        return 1;
    }

    // Bind the socket to the specified interface address and port
    struct sockaddr_in interfaceAddr;
    std::memset(&interfaceAddr, 0, sizeof(interfaceAddr));
    interfaceAddr.sin_family = AF_INET;
    interfaceAddr.sin_addr.s_addr = inet_addr(interfaceAddress);
    interfaceAddr.sin_port = htons(port);

    // Set IP_MULTICAST_IF option
    if (inet_pton(AF_INET, interfaceAddress, &interfaceAddr) != 1) {
        std::cerr << "Failed to convert interface address." << std::endl;
        close(sock);
        return 1;
    }

    // Set socket options for multicast
    int ttl = 1;
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
        std::cerr << "Failed to set TTL." << std::endl;
        close(sock);
        return 1;
    }

    // Set up the multicast group address and port
    struct sockaddr_in multicastAddr;
    std::memset(&multicastAddr, 0, sizeof(multicastAddr));
    multicastAddr.sin_family = AF_INET;
    multicastAddr.sin_addr.s_addr = inet_addr(multicastAddress);
    multicastAddr.sin_port = htons(port);

    if (bind(sock, (struct sockaddr*)&multicastAddr, sizeof(multicastAddr)) < 0) {
        std::cerr << "Failed to bind socket to multicast address." << std::endl;
        close(sock);
        return 1;
    }

    // Join the multicast group
    struct ip_mreq multicastRequest;
    multicastRequest.imr_multiaddr.s_addr = inet_addr(multicastAddress);
    multicastRequest.imr_interface.s_addr = inet_addr(interfaceAddress);

    if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&multicastRequest, sizeof(multicastRequest)) < 0) {
        std::cerr << "Failed to join multicast group." << std::endl;
        close(sock);
        return 1;
    }

    std::cout << "Socket successfully opened on multicast address " << multicastAddress << " and port " << port << " with interface address " << interfaceAddress << "." << std::endl;

    char buffer[1500];
    struct sockaddr_in senderAddr;
    socklen_t senderAddrLen = sizeof(senderAddr);

    while (true) {
        // Receive a UDP datagram
        ssize_t numBytes = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&senderAddr, &senderAddrLen);
        if (numBytes < 0) {
            std::cerr << "Failed to receive data." << std::endl;
            close(sock);
            return 1;
        }

        buffer[numBytes] = '\0';

        // Dump the contents of the received message
        std::cout << "Received " << numBytes << " bytes from " << inet_ntoa(senderAddr.sin_addr) << ":" << ntohs(senderAddr.sin_port) << std::endl;
        std::cout << "Message: " << std::endl;
        size_t len = (numBytes < sizeof(buffer)) ? numBytes : sizeof(buffer);
        printBufferAsHex(buffer, len);
        std::cout << "==========================================" << std::endl;

        // Continue listening for more datagrams
    }

    // Cleanup and close the socket
    close(sock);

    return 0;
}

```

Compiling (i’m on Mac)

```auto
clang++ -std=c++14 -I/usr/include multicast.cpp -o multicast

```

Running:

```auto
./multicast 172.16.0.100 239.0.29.85 9382

```

I didn’t need to be `sudo` to receive.

---

<div class="post-metadata">

**Author:** ![erik.rainey](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/erik.rainey/32/20_2.png) [@erik.rainey](https://forum.opencyphal.org/u/erik.rainey)\
**Post date:** [June 10, 2023, 10:12pm UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/2 "2023-06-10T22:12:33Z")

</div>

![Screenshot 2023-06-10 at 4.49.01 PM](https://forum.opencyphal.org/uploads/default/original/2X/e/e80ca56e4779a3d6579e8a42b65733f1b96b3502.png)

---

<div class="post-metadata">

**Author:** ![scottdixon](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/scottdixon/32/39_2.png) [@scottdixon](https://forum.opencyphal.org/u/scottdixon)\
**Post date:** [June 10, 2023, 11:49pm UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/3 "2023-06-10T23:49:30Z")

</div>

![image](https://forum.opencyphal.org/uploads/default/original/2X/1/11b7aa37bd3892f2a3dc954ef6fb4156e2646932.jpeg)

---

<div class="post-metadata">

**Author:** ![erik.rainey](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/erik.rainey/32/20_2.png) [@erik.rainey](https://forum.opencyphal.org/u/erik.rainey)\
**Post date:** [June 11, 2023, 1:28am UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/4 "2023-06-11T01:28:53Z")

</div>

With some changes posted above i can have two socket listeners (same program running twice) on the same multicast IP/port (each gets a copy). I’m still perplexed why pycyphal can’t receive data on it’s multicast sockets for this same data.

---

<div class="post-metadata">

**Author:** ![erik.rainey](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/erik.rainey/32/20_2.png) [@erik.rainey](https://forum.opencyphal.org/u/erik.rainey)\
**Post date:** [June 11, 2023, 1:30am UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/5 "2023-06-11T01:30:31Z")

</div>

I asked ChatGPT _nicely_ this time to convert the C++ to python and after some iteration with changing out the local interface address with the multicast address, I can see heart beart with this simple script too:

```python
import argparse
import socket
import sys

def print_buffer_as_hex(buffer):
    counter = 0
    for byte in buffer:
        print(f'{byte:02X}', end=' ')
        counter += 1
        if counter == 16:
            print()
            counter = 0
    if counter != 0:
        print()

def main():
    # Argument parsing
    parser = argparse.ArgumentParser(description='UDP Multicast Receiver')
    parser.add_argument('interface_address', type=str, help='Interface address to bind the socket')
    parser.add_argument('multicast_address', type=str, help='Multicast group address')
    parser.add_argument('port', type=int, help='Port number')
    parser.add_argument('--ttl', type=int, default=1, help='Multicast TTL')
    args = parser.parse_args()

    interface_address = args.interface_address
    multicast_address = args.multicast_address
    port = args.port

    # Create socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # Set SO_REUSEADDR option
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # Set SO_REUSEPORT option if available (Linux-specific)
    if hasattr(socket, 'SO_REUSEPORT'):
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

    # Bind to multicast address and port
    sock.bind((multicast_address, port))

    # Set IP_ADD_MEMBERSHIP option to join multicast group
    group = socket.inet_aton(multicast_address)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, group + socket.inet_aton(interface_address))

    print(f'Joined multicast group {multicast_address}')

    # Set SO_MULTICAST_TTL option
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, args.ttl)

    # Set SO_MULTICAST_IF option
    interface_ip = socket.inet_aton(interface_address)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, interface_ip)

    print(f"Socket successfully opened on multicast address {multicast_address} and port {port} with interface address {interface_address}.")

    # Receive and print data
    buffer_size = 1500
    buffer = bytearray(buffer_size)

    while True:
        try:
            num_bytes, addr = sock.recvfrom_into(buffer)
            if num_bytes == 0:
                break
            print(f'Received {num_bytes} bytes from {addr[0]}:{addr[1]}')
            print_buffer_as_hex(buffer[:num_bytes])
        except KeyboardInterrupt:
            break

    sock.close()

if __name__ == ' __main__':
    main()

```

---

<div class="post-metadata">

**Author:** ![erik.rainey](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/erik.rainey/32/20_2.png) [@erik.rainey](https://forum.opencyphal.org/u/erik.rainey)\
**Post date:** [June 11, 2023, 2:01am UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/6 "2023-06-11T02:01:53Z")

</div>

So in the end here we can see that this at least proves in theory there’s no other configuration problem other than the local _firewalls and packet filters_ getting in the way.

On Mac `sudo pfctl -d -i 100000000000000000`🙂 can’t disable for ever  
On Linux `sudo ufw disable`

I’ll ask Chat how to construct rules for the packet filter to allow multicast through

---

<div class="post-metadata">

**Author:** ![erik.rainey](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/erik.rainey/32/20_2.png) [@erik.rainey](https://forum.opencyphal.org/u/erik.rainey)\
**Post date:** [June 11, 2023, 2:53am UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/7 "2023-06-11T02:53:04Z")

</div>

Added pfctl rules here: [Mac OSX `pfctl` rule for Cyphal/UDP - #2 by erik.rainey](https://forum.opencyphal.org/t/mac-osx-pfctl-rule-for-cyphal-udp/1908/2)

---

<div class="post-metadata">

**Author:** ![maksim.drachov](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/maksim.drachov/32/1577_2.png) [@maksim.drachov](https://forum.opencyphal.org/u/maksim.drachov)\
**Post date:** [June 11, 2023, 6:13am UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/8 "2023-06-11T06:13:12Z")

</div>

Not sure if this is related, but there’s something similar when developing pycyphal on macOS:

[https://pycyphal.readthedocs.io/en/stable/pages/dev.html#setup](https://pycyphal.readthedocs.io/en/stable/pages/dev.html#setup)

(See the green tip box)

---

<div class="post-metadata">

**Author:** ![erik.rainey](https://forum.opencyphal.org/user_avatar/forum.opencyphal.org/erik.rainey/32/20_2.png) [@erik.rainey](https://forum.opencyphal.org/u/erik.rainey)\
**Post date:** [June 12, 2023, 5:24pm UTC](https://forum.opencyphal.org/t/simple-c-udp-listener-for-debugging/1907/9 "2023-06-12T17:24:44Z")

</div>

I wireshark installed w/ the Cyphal/UDP plugin. I can see the remove devices on the network respond to the Service request but pycyphal doesn’t does not receive them. The simple programs above _do_ see those service response when you run them on those other (239.1.0.100) multicast addresses.
