Can't send UDP packages to other addresses than the global broadcast address!

I'm trying to send UDP data to specific addresses to avoid broadcast flooding in the local network, but unfortunately only the global broadcast address 255.255.255.255 works!

In the program bellow there are three types of addresses, and only the third one is received.

The destination machine has a manually set ip, and I'm able to ping it on 192.168.31.22!

This is the code:

#include <EtherCard.h>

// Network settings
byte mymac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};    // MAC address
byte myip[] = {192, 168, 31, 100};                      // Arduino IP
const uint16_t PORT = 5005;                             // UDP port

// Ethernet buffer
byte Ethernet::buffer[500];  // This is the crucial missing piece!

void setup() {
    Serial.begin(9600);
    Serial.println("\n");

    // Initialize Ethernet (CS pin = 10 for Uno)
    if (!ether.begin(sizeof(Ethernet::buffer), mymac, 10)) {
        Serial.println("Failed to initialize ENC28J60!");
        while(1);
    }
    
    // Set static IP (disable DHCP)
    ether.staticSetup(myip);
    
    Serial.println("Ethernet initialized");
}

void loop() {
    
    // 1. DOESN'T WORK :(

    const char* data_specific = "1. Single Address!";
    const byte single_ip[]  = {192, 168, 31, 22};       // Single destination IP
    delay(2000); // Send every 2 seconds
    Serial.println(data_specific);
    ether.sendUdp(
        data_specific,          // Payload
        strlen(data_specific),  // Length
        PORT,                   // Source port
        single_ip,              // Destination IP
        PORT                    // Destination port
    );
    
    // 2. DOESN'T WORK :(

    const char* data_network = "2. Network Address!";
    const byte network_ip[] = {192, 168, 31, 255};      // Network destination IP (broadcast)
    delay(2000); // Send every 2 seconds
    Serial.println(data_network);
    ether.sendUdp(
        data_network,           // Payload
        strlen(data_network),   // Length
        PORT,                   // Source port
        network_ip,             // Destination IP
        PORT                    // Destination port
    );
    
    // 3. IT WORKS :)

    const char* data_global = "3. Global Address!";
    const byte global_ip[]  = {255, 255, 255, 255};     // Global destination IP (broadcast)
    delay(2000); // Send every 2 seconds
    Serial.println(data_global);
    ether.sendUdp(
        data_global,            // Payload
        strlen(data_global),    // Length
        PORT,                   // Source port
        global_ip,              // Destination IP
        PORT                    // Destination port
    );
}

Thanks.

Don't you need to set a multicast option somewhere for a non-broadcast UDP frame?

I needed to set the mask, as 255.255.255.0, and then the network broadcast, the second, worked. However, the single address still doesn't work. Does it have to be configured to use DHCP instead, in order to translate MAC addresses correctly or something?

Here is the updated code, just one failing now:

#include <EtherCard.h>

// Network settings
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};      // MAC address
byte my_ip[] = {192, 168, 31, 100};                     // Arduino IP
byte gw_ip[] = {192, 168, 31, 77};                      // IP of the main router, gateway
byte dns_ip[] = {192, 168, 31, 77};                     // DNS address is the same as the gateway router
byte mask[] = {255, 255, 255, 0};                       // NEEDED FOR NETWORK BROADCAST
const uint16_t PORT = 5005;                             // UDP port

// Ethernet buffer
byte Ethernet::buffer[500];  // This is the crucial missing piece!

void setup() {
    Serial.begin(9600);
    Serial.println("\n");

    // Initialize Ethernet (CS pin = 10 for Uno)
    if (!ether.begin(sizeof(Ethernet::buffer), mac, 10)) {
        Serial.println("Failed to initialize ENC28J60!");
        while(1);
    }
    
    // Set static IP (disable DHCP)
    ether.staticSetup(my_ip, gw_ip, dns_ip, mask);
    
    Serial.println("Ethernet initialized");
}

void loop() {
    
    // 1. DOESN'T WORK :(

    const char* data_single = "1. Single Address!";
    const byte single_ip[]  = {192, 168, 31, 22};       // Single destination IP
    delay(2000); // Send every 2 seconds
    Serial.println(data_single);
    ether.sendUdp(
        data_single,            // Payload
        strlen(data_single),    // Length
        PORT,                   // Source port
        single_ip,              // Destination IP
        PORT                    // Destination port
    );
    
    // 2. IT WORKS :)

    const char* data_network = "2. Network Address!";
    const byte network_ip[] = {192, 168, 31, 255};      // Network destination IP (broadcast)
    delay(2000); // Send every 2 seconds
    Serial.println(data_network);
    ether.sendUdp(
        data_network,           // Payload
        strlen(data_network),   // Length
        PORT,                   // Source port
        network_ip,             // Destination IP
        PORT                    // Destination port
    );
    
    // 3. IT WORKS :)

    const char* data_global = "3. Global Address!";
    const byte global_ip[]  = {255, 255, 255, 255};     // Global destination IP (broadcast)
    delay(2000); // Send every 2 seconds
    Serial.println(data_global);
    ether.sendUdp(
        data_global,            // Payload
        strlen(data_global),    // Length
        PORT,                   // Source port
        global_ip,              // Destination IP
        PORT                    // Destination port
    );
}

Based on your latest result, should the mask then be 255,255,255,255

That's not a valid network mask, a network mask is a series of 4 bytes and always needs to have binary zeroes at the end of it to define the range of available IPs, like:
11111111.11111111.11111111.11110000 = 255.255.255.240

Anyway, as you previously suggested, I added the line ether.enableBroadcast() after the ether.staticSetup and made no difference, maybe because it's about broadcast and not single addresses.

Why use that library, the Arduino Ethernet Library will do what you want.

Because it's a ENC28J60 module I'm using, and this library is the one needed for it.

In that case peruse the library source to discover how it supports single address UDP, IIRC it's BeginPacket in normal UDP.

Probably a dumb idea, but what happens if the mask is the same as the single IP?

No, the problem is entirely related to the MAC address, for some reason the library can't get the registered MAC address in this EtherCard method, at least, that's what I think, given that these variables, like destmacaddr, are private and I can't have access to them from the main sketch.

void EtherCard::clientIcmpRequest(const uint8_t *destip) {
    if(is_lan(EtherCard::myip, destip)) {
        setMACandIPs(destmacaddr, destip);
    } else {
        setMACandIPs(gwmacaddr, destip);
    }
    gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V;
    gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V;
    memcpy_P(gPB + IP_P,iphdr,sizeof iphdr);
    gPB[IP_TOTLEN_L_P] = 0x54;
    gPB[IP_PROTO_P] = IP_PROTO_ICMP_V;
    fill_ip_hdr_checksum();
    gPB[ICMP_TYPE_P] = ICMP_TYPE_ECHOREQUEST_V;
    gPB[ICMP_TYPE_P+1] = 0; // code
    gPB[ICMP_CHECKSUM_H_P] = 0;
    gPB[ICMP_CHECKSUM_L_P] = 0;
    gPB[ICMP_IDENT_H_P] = 5; // some number
    gPB[ICMP_IDENT_L_P] = EtherCard::myip[3]; // last byte of my IP
    gPB[ICMP_IDENT_L_P+1] = 0; // seq number, high byte
    gPB[ICMP_IDENT_L_P+2] = 1; // seq number, low byte, we send only 1 ping at a time
    memset(gPB + ICMP_DATA_P, PINGPATTERN, 56);
    fill_checksum(ICMP_CHECKSUM_H_P, ICMP_TYPE_P, 56+8,0);
    packetSend(98);
}

It started working out of the blue, the only change I made was on the destination PC, with the IP 192.168.31.22, where I changed it from a static address to an automatic one, as DHCP, and then, in the router, I associated that same address to the destination PC's MAC address.

I'm still not sure if the problem isn't my module that is malfunctioning instead, an ENC28J60, and the changing of my PC's IP configuration was just a coincidence!... Nevertheless, this is the final sketch that worked!

#include <EtherCard.h>

#define USE_STATIC_IP

// Network settings
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};      // MAC address
byte my_ip[] = {192, 168, 31, 100};                     // Arduino IP
byte gw_ip[] = {192, 168, 31, 77};                      // IP of the main router, gateway
byte dns_ip[] = {192, 168, 31, 77};                     // DNS address is the same as the gateway router
byte mask[] = {255, 255, 255, 0};                       // NEEDED FOR NETWORK BROADCAST
const uint16_t PORT = 5005;                             // UDP port

// Ethernet buffer
byte Ethernet::buffer[500];  // This is the crucial missing piece!

void setup() {
    Serial.begin(9600);
    Serial.println("\n");

    // Initialize Ethernet (CS pin = 10 for Uno)
    if (!ether.begin(sizeof(Ethernet::buffer), mac, 10)) {
        Serial.println("Failed to initialize ENC28J60!");
        while(1);
    }
    
    #ifdef USE_STATIC_IP    
    // Set static IP (disable DHCP)
    ether.staticSetup(my_ip, gw_ip, dns_ip, mask);
    #else
    // DHCP Configuration
    Serial.println("Requesting DHCP lease...");
    if (!ether.dhcpSetup()) {
        Serial.println("DHCP failed!");
        while(1);
    }

    // Print network configuration
    Serial.println("DHCP assigned configuration:");
    Serial.print("IP: "); ether.printIp(ether.myip); Serial.println();
    Serial.print("GW: "); ether.printIp(ether.gwip); Serial.println();
    Serial.print("DNS: "); ether.printIp(ether.dnsip); Serial.println();
    Serial.print("NETMASK: "); ether.printIp(ether.netmask); Serial.println();
    #endif

    // Makes sure it allows broadcast
    ether.enableBroadcast();
    
    Serial.println("Ethernet initialized");
}

void loop() {
    
    // 1. WORKS AFTER SETTING THE DESTINATION PC ADDRESS AS DHCP INSTEAD OF STATIC :/
    // (EFFECT NOT IMMEDIATE THOUGH)

    const char* data_single = "1. Single Address!";
    const byte single_ip[]  = {192, 168, 31, 22};       // Single destination IP
    delay(2000); // Send every 2 seconds
    Serial.println(data_single);
    ether.sendUdp(
        data_single,            // Payload
        strlen(data_single),    // Length
        PORT,                   // Source port
        single_ip,              // Destination IP
        PORT                    // Destination port
    );
    
    // 2. IT WORKS :)

    const char* data_network = "2. Network Address!";
    const byte network_ip[] = {192, 168, 31, 255};      // Network destination IP (broadcast)
    delay(2000); // Send every 2 seconds
    Serial.println(data_network);
    ether.sendUdp(
        data_network,           // Payload
        strlen(data_network),   // Length
        PORT,                   // Source port
        network_ip,             // Destination IP
        PORT                    // Destination port
    );
    
    // 3. IT WORKS :)

    const char* data_global = "3. Global Address!";
    const byte global_ip[]  = {255, 255, 255, 255};     // Global destination IP (broadcast)
    delay(2000); // Send every 2 seconds
    Serial.println(data_global);
    ether.sendUdp(
        data_global,            // Payload
        strlen(data_global),    // Length
        PORT,                   // Source port
        global_ip,              // Destination IP
        PORT                    // Destination port
    );
}

It worked because I had the Wireshark opened, when I close it it stops working right away. So, the only solution is to try a different library than the EtherCard one!

you could try the EthernetENC library (I maintain it)

Yes, I noticed that library, however I didn't start to use it right away because I'm doing this small project in an Arduino nano and due to its lack of Flash and RAM memory isn't possible to use anything more heavy, so I end up keeping the EtherCard but in broadcast IP response.

When I start to program the other Arduino board, an ESP8266, then I will use that one that is able to target single addresses. I even tested it already and it worked just fine. The code I tried was this one:

#include <EthernetENC.h>
#include <EthernetUdp.h>

// MAC address for ENC28J60 module (must be unique on the network)
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

// Local IP for the Arduino
IPAddress localIP(192, 168, 31, 100);

// Destination IP and port
IPAddress destIP(192, 168, 31, 22);  // <-- Replace with your PC's IP
unsigned int destPort = 5005;       // <-- Replace with target UDP port

EthernetUDP Udp;

void setup() {
  Serial.begin(9600);

  // Start Ethernet with static IP
  Ethernet.begin(mac, localIP);
  delay(1000);  // Give time to initialize

  Serial.println("Ethernet initialized");
  
  Udp.begin(5005); // Local UDP port (can be arbitrary)
  
  sendUDP();
}

void loop() {
  delay(2000);
  sendUDP();
}

void sendUDP() {
  const char msg[] = "Hello, UDP!";
  Udp.beginPacket(destIP, destPort);
  Udp.write((const uint8_t*)msg, sizeof(msg));
  Udp.endPacket();

  Serial.println("UDP packet sent");
}

https://github.com/Networking-for-Arduino/EthernetENC/wiki/Settings

I'm trying to make this sketch work...

#include <EthernetENC.h>
#include <EthernetUdp.h>

// // Linux testing commands:
// echo "BROADCAST 255" | nc -ubv 255.255.255.255 5005
// echo "BROADCAST 192" | nc -ubv 192.168.31.255 5005
// echo "UNICAST" | nc -ubv 192.168.31.100 5005


// Network Settings
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress ip(192, 168, 31, 100);       // Arduino IP
IPAddress subnet(255, 255, 255, 0);   // Network mask
IPAddress gateway(192, 168, 31, 77);    // Router IP (if needed)
const unsigned int UDP_PORT = 5005;
IPAddress target(192, 168, 31, 22);                // Target PC IP
IPAddress broadcast(255, 255, 255, 255); // Broadcast address

EthernetUDP udp;
unsigned long lastSendTime = 0;
const unsigned long INTERVAL = 5000; // 5 seconds

void setup() {
    Serial.begin(9600);
    
    // Initialize Ethernet with static IP
    Ethernet.begin(mac, ip, gateway, subnet);

    // Start UDP
    if (udp.begin(UDP_PORT)) {
        Serial.print("\n\nUDP active on ");
        Serial.println(Ethernet.localIP());
    } else {
        Serial.println("UDP failed!");
    }
}

void loop() {

    // Send "Hello" every 5 seconds
    if (millis() - lastSendTime >= INTERVAL) {
        if (random(2) % 2 == 0) {
            Serial.println("Direct hello");
            udp.beginPacket(target, UDP_PORT);
            udp.write("Direct hello");
        } else {
            Serial.println("Broadcasted hello");
            udp.beginPacket(broadcast, UDP_PORT);
            udp.write("Broadcasted hello");
        }
        udp.endPacket();
        lastSendTime = millis();
    }

    
    // Check for incoming packets
    int packetSize = udp.parsePacket();
    if (packetSize > 0) {
        char packet[128];
        udp.read(packet, sizeof(packet));
        Serial.print("From ");
        Serial.print(udp.remoteIP());
        Serial.print(": ");
        Serial.println(packet);
    }
}

However, the received packages in the other machine are these:

b'Direct helloBroadca'
b'Broadcasted hello'
b'Direct helloBroadca'
b'Direct helloBroadca'
b'Broadcasted hello'
b'Broadcasted hello'
b'Direct helloDirect Direct Direct Direct Direct Direct Broadca'
b'Direct helloDirect Broadca'
b'Direct helloBroadca'
b'Broadcasted hello'
b'Broadcasted hello'
b'Direct helloDirect Broadca'
b'Direct helloBroadca'
b'Broadcasted hello'

And the Arduino nano never receives any package I send to it, regardless from the Python script or from these commands:

echo "BROADCAST 255" | nc -ubv 255.255.255.255 5005
echo "BROADCAST 192" | nc -ubv 192.168.31.255 5005
echo "UNICAST" | nc -ubv 192.168.31.100 5005

Ping works just fine, so it must be a problem with UDP implementation!

Pinging 192.168.31.100 with 32 bytes of data:
Reply from 192.168.31.100: bytes=32 time=1ms TTL=128
Reply from 192.168.31.100: bytes=32 time=1ms TTL=128
Reply from 192.168.31.100: bytes=32 time=1ms TTL=128
Reply from 192.168.31.100: bytes=32 time=1ms TTL=128

Ping statistics for 192.168.31.100:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 1ms, Maximum = 1ms, Average = 1ms

Can you help me in make the script work, these is the compiling output.

Sketch uses 16456 bytes (53%) of program storage space. Maximum is 30720 bytes.
Global variables use 1054 bytes (51%) of dynamic memory, leaving 994 bytes for local variables. Maximum is 2048 bytes.

Thanks

In the end I had to use the library UIPEthernet to make it fully work!

One thing I noticed about these libraries is their lack of documentation and examples, sadly!

#include <UIPEthernet.h>
#include <UIPUdp.h>  // If using UDP

#define UDP_TX_PACKET_MAX_SIZE 512

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 31, 100);
IPAddress subnet(255, 255, 255, 0);
IPAddress target(192, 168, 31, 22);
unsigned int UDP_PORT = 5005;

EthernetUDP Udp;

void setup() {
    Serial.begin(9600);
    Ethernet.begin(mac, ip, subnet);
    Udp.begin(UDP_PORT);
    Serial.println("UDP Listener Ready");
}

void loop() {
    int packetSize = Udp.parsePacket();
    if (packetSize > 0) {
        char packet[UDP_TX_PACKET_MAX_SIZE];
        int len = Udp.read(packet, min(packetSize, UDP_TX_PACKET_MAX_SIZE - 1));
        packet[len] = '\0';
        Serial.print("Received: ");
        Serial.println(packet);
    }

    static unsigned long lastSend = 0;
    if (millis() - lastSend > 20000) {
        lastSend = millis();
        if (random(2) % 2 == 0) {
            Udp.beginPacket(IPAddress(255,255,255,255), UDP_PORT);
            Udp.write("Broadcasted Hello!");
            Udp.endPacket();
            Serial.println("Broadcast Sent");
        } else {
            Udp.beginPacket(target, UDP_PORT);
            Udp.write("Unicasted Hello!");
            Udp.endPacket();
            Serial.println("Unicast Sent");
        }
        // Udp.flush(); // Clear any remaining data
        // Udp.begin(UDP_PORT); // Refresh socket
    }
}

https://github.com/Networking-for-Arduino/EthernetENC/wiki