Chapter 92: Multicast and Broadcast Communication

Chapter Objectives

After completing this chapter, you will be able to:

  • Understand the concepts of broadcast and multicast communication.
  • Differentiate between broadcast and multicast addresses and their uses.
  • Configure and use sockets for sending and receiving broadcast UDP packets.
  • Configure and use sockets for sending and receiving multicast UDP packets.
  • Understand and use relevant socket options like SO_BROADCAST, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, and IP_MULTICAST_TTL.
  • Grasp the basics of the Internet Group Management Protocol (IGMP) and its role in multicast.
  • Implement simple applications using broadcast (e.g., service discovery) and multicast (e.g., data dissemination).
  • Identify common issues and troubleshoot broadcast/multicast applications on ESP32.

Introduction

In the preceding chapters, we primarily focused on unicast communication—where one device sends data to another single device. However, many network applications require one-to-many communication. Imagine needing to discover services on a local network without knowing their specific IP addresses, or wanting to stream sensor data to multiple interested listeners simultaneously without sending individual copies to each. These scenarios are where broadcast and multicast communication become invaluable.

Broadcast allows a device to send a single packet that is received by all other devices on the same local network segment. It’s like making an announcement over a PA system in a single room – everyone present hears it.

Multicast is a more refined approach, allowing a device to send a single packet to a specific group of interested recipient devices. These recipients explicitly “join” a multicast group to receive the packets. This is akin to a radio station broadcasting on a specific frequency; only those who tune their radios to that frequency will receive the transmission.

This chapter will explore the theory behind IPv4 broadcast and multicast, how they are implemented using the socket API in ESP-IDF, and practical examples to demonstrate their use on ESP32 devices. Understanding these mechanisms is crucial for developing applications like network discovery protocols (e.g., mDNS, which builds upon multicast), group messaging, and efficient data distribution.

Theory

Broadcast Communication

Broadcast is a one-to-all communication method within a specific network segment. When a packet is broadcast, every host on that network segment receives and processes it (up to the network layer).

IPv4 Broadcast Addresses

In IPv4, there are two main types of broadcast addresses:

  1. Limited Broadcast Address (255.255.255.255):
    • Packets sent to this address are delivered to all hosts on the same physical network segment as the sender.
    • Routers do not forward packets destined for 255.255.255.255. This keeps the broadcast traffic strictly local.
    • This is the most common type of broadcast address used in applications.
  2. Directed Broadcast Address:
    • This address consists of the network prefix of a specific target network with all host bits set to 1. For example, for a network 192.168.1.0 with a subnet mask 255.255.255.0 (or /24), the directed broadcast address is 192.168.1.255.
    • Historically, packets sent to a directed broadcast address were intended to be forwarded by routers to the specified network, where they would then be broadcast to all hosts on that target network.
    • Security Concern: Due to security risks (e.g., Smurf attacks), most modern routers disable the forwarding of directed broadcasts by default. Therefore, their utility in wide-area networks is very limited, and they primarily behave like limited broadcasts if used within the local network.
Feature Limited Broadcast Address Directed Broadcast Address
Example Address 255.255.255.255 e.g., 192.168.1.255 for network 192.168.1.0/24
Scope All hosts on the same local physical network segment as the sender. All hosts on a specific target network segment.
Router Forwarding Not forwarded by routers. Traffic remains strictly local to the sender’s segment. Historically, routers could forward these to the target network. However, this is disabled by default on most modern routers due to security concerns (e.g., Smurf attacks).
Common Use Most common type for application-level broadcasts (e.g., service discovery on the local LAN). Rarely used across different networks due to router restrictions. If used within the target network itself, behaves like a limited broadcast.
Configuration No special network configuration needed beyond being on the same segment. Requires knowledge of the target network’s prefix and host bit configuration.
Security Implication Lower risk as it’s confined to the local segment. Higher risk if forwarded by routers (amplification attacks), hence largely deprecated for inter-network use.

graph TB
    subgraph LAN["Local Area Network (192.168.1.0/24)"]
        Sender["Host A<br/>192.168.1.10<br/>📱 Sender"]
        Host2["Host B<br/>192.168.1.20<br/>💻"]
        Host3["Host C<br/>192.168.1.30<br/>🖥️"]
        Host4["Host D<br/>192.168.1.40<br/>📟"]
        Switch["Switch<br/>🔄"]
        
        Sender -.-> Switch
        Host2 -.-> Switch
        Host3 -.-> Switch
        Host4 -.-> Switch
    end
    
    Router["Router<br/>192.168.1.1<br/>🌐"]
    Internet["Internet<br/>🌍"]
    
    Switch -.-> Router
    Router -.-> Internet
    
    %% Broadcast arrows from sender to all hosts
    Sender -->|"Broadcast to<br/>255.255.255.255"| Host2
    Sender -->|"Broadcast to<br/>255.255.255.255"| Host3
    Sender -->|"Broadcast to<br/>255.255.255.255"| Host4
    
    %% Router blocking broadcast
    Sender -.->|"❌ Blocked<br/>Not forwarded"| Router
    
    %% Styling
    classDef senderStyle fill:#e1f5fe,stroke:#01579b,stroke-width:3px
    classDef hostStyle fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    classDef routerStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef switchStyle fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
    classDef internetStyle fill:#fce4ec,stroke:#880e4f,stroke-width:2px
    
    class Sender senderStyle
    class Host2,Host3,Host4 hostStyle
    class Router routerStyle
    class Switch switchStyle
    class Internet internetStyle

MAC Layer Broadcast

At the Data Link Layer (Layer 2), Ethernet frames also have a broadcast MAC address: FF:FF:FF:FF:FF:FF. When an IP broadcast packet (e.g., to 255.255.255.255) is to be sent on an Ethernet-like network (including Wi-Fi), it is encapsulated in a frame with this destination MAC address. Network interface cards (NICs) are programmed to accept frames with their own unicast MAC address and the broadcast MAC address.

Using Sockets for Broadcast (UDP)

Broadcast is typically used with connectionless protocols like UDP. TCP, being connection-oriented, is not suitable for broadcast.

Aspect Sending Broadcasts Receiving Broadcasts
Socket Type SOCK_DGRAM (UDP) SOCK_DGRAM (UDP)
Key Socket Option(s) SO_BROADCAST (must be enabled)
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable))
SO_REUSEADDR (optional, but often useful to allow multiple listeners on the same port)
Address for sendto() / bind() Destination for sendto():
  • Limited Broadcast: 255.255.255.255
  • Directed Broadcast: e.g., 192.168.1.255 (use with caution)
Address for bind():
  • IP: INADDR_ANY (typically) or a specific interface IP.
  • Port: The port on which broadcasts are expected.
Core Function(s) socket(), setsockopt(), sendto() socket(), bind(), recvfrom()
Considerations Ensure SO_BROADCAST is set, otherwise sendto() to a broadcast address will likely fail (Permission Denied). The socket must be bound to the correct port to receive broadcasts. INADDR_ANY allows receiving on any interface.
  • Sending Broadcasts:
    1. Create a UDP socket (SOCK_DGRAM).
    2. Enable the SO_BROADCAST socket option. This is mandatory; without it, the operating system will typically prevent sending to a broadcast address.
      int enable_broadcast = 1;
      if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &enable_broadcast, sizeof(enable_broadcast)) < 0) {
      ESP_LOGE(TAG, "setsockopt(SO_BROADCAST) failed: %s", strerror(errno));
      // Handle error
      }
    3. Use sendto() with the destination address set to 255.255.255.255 (or a directed broadcast address, with caution) and the target port.
  • Receiving Broadcasts:
    1. Create a UDP socket.
    2. Bind the socket to INADDR_ANY (or a specific local IP address) and the port on which broadcasts are expected. INADDR_ANY (0.0.0.0) means the socket will receive packets destined for that port on any of the host’s network interfaces.
    3. Use recvfrom() to receive packets. The source address information will be filled by recvfrom().

Multicast Communication

Multicast is a one-to-many communication method where data is sent from one source to multiple specific recipients that have expressed interest in receiving it. This is more efficient than sending multiple unicast packets and more targeted than broadcast.

IPv4 Multicast Addresses

Address Range Name / Scope Common Examples / Notes
224.0.0.0 to 224.0.0.255 Well-Known / Local Network Control Block Reserved for network protocols on the local segment.
  • 224.0.0.1: All Hosts on this subnet
  • 224.0.0.2: All Routers on this subnet
  • 224.0.0.251: mDNS (Multicast DNS)
  • 224.0.0.252: LLMNR (Link-Local Multicast Name Resolution)
Packets typically have TTL=1 and are not forwarded by routers.
224.0.1.0 to 224.0.1.255 Internetwork Control Block Used for control traffic that may be routed across the internet (e.g., NTP at 224.0.1.1).
224.0.2.0 to 231.255.255.255 AD-HOC Block I, II, III / Globally Scoped (Potentially) General purpose multicast addresses that can be routed across the internet if supported by ISPs and network infrastructure. Used for applications like streaming media, online gaming. Includes Source Specific Multicast (SSM) range 232.0.0.0/8.
232.0.0.0 to 232.255.255.255 Source-Specific Multicast (SSM) Block A subset of globally scoped addresses. In SSM, receivers explicitly specify both the group address and the source address(es) they want to receive from. Requires IGMPv3.
233.0.0.0 to 233.255.255.255 GLOP Addressing Block Addresses allocated based on Autonomous System (AS) numbers, for organizations with their own AS to have a unique block of multicast addresses.
234.0.0.0 to 238.255.255.255 Unicast-Prefix-Based IPv4 Multicast Addresses / Globally Scoped Further ranges for globally routable multicast.
239.0.0.0 to 239.255.255.255 Administratively Scoped / Limited Scope / Private Use Intended for private use within an organization or site, similar to private IP ranges (RFC 1918). Not meant to be routed globally. Often used for enterprise applications.
  • IPv4 multicast addresses are in the Class D range: 224.0.0.0 to 239.255.255.255.
  • These addresses identify a “host group” or multicast group, not a single host.
  • Well-Known Multicast Addresses (224.0.0.0 to 224.0.0.255): These are reserved for specific network protocols on the local network segment (e.g., 224.0.0.1 is All Hosts, 224.0.0.2 is All Routers, 224.0.0.251 is mDNS). Packets to these addresses are typically not forwarded by routers (TTL is often 1).
  • Globally Scoped Addresses (224.0.1.0 to 238.255.255.255): These can theoretically be routed across the internet, forming global multicast groups (though ISP support varies).
  • Limited Scope Addresses (239.0.0.0 to 239.255.255.255): These are typically for private use within an organization or site, similar to private IP address ranges.
graph TB
    subgraph Network1["Network A (192.168.1.0/24)"]
        Sender["Sender<br/>192.168.1.10<br/>📱"]
        Switch1["Switch<br/>🔄"]
        Sender -.-> Switch1
    end
    
    subgraph Network2["Network B (192.168.2.0/24)"]
        Host1["Host B1<br/>192.168.2.10<br/>💻<br/>✅ Joined 239.1.2.3"]
        Host2["Host B2<br/>192.168.2.20<br/>🖥️<br/>❌ Not in group"]
        Host3["Host B3<br/>192.168.2.30<br/>📟<br/>✅ Joined 239.1.2.3"]
        Switch2["Switch<br/>🔄"]
        
        Host1 -.-> Switch2
        Host2 -.-> Switch2
        Host3 -.-> Switch2
    end
    
    subgraph Network3["Network C (192.168.3.0/24)"]
        Host4["Host C1<br/>192.168.3.10<br/>💻<br/>❌ Not in group"]
        Host5["Host C2<br/>192.168.3.20<br/>🖥️<br/>❌ Not in group"]
        Switch3["Switch<br/>🔄"]
        
        Host4 -.-> Switch3
        Host5 -.-> Switch3
    end
    
    Router["Multicast Router<br/>🌐<br/>IGMP Enabled"]
    
    Switch1 -.-> Router
    Switch2 -.-> Router
    Switch3 -.-> Router
    
    %% Multicast flow
    Sender -->|"Send to<br/>239.1.2.3"| Router
    Router -->|"Replicate &<br/>Forward"| Switch2
    Router -.->|"❌ No members<br/>Not forwarded"| Switch3
    
    %% To group members only
    Switch2 -->|"Multicast<br/>239.1.2.3"| Host1
    Switch2 -->|"Multicast<br/>239.1.2.3"| Host3
    Switch2 -.->|"🚫 Ignored<br/>Not in group"| Host2
    
    %% IGMP membership
    Host1 -.->|"IGMP Join<br/>239.1.2.3"| Router
    Host3 -.->|"IGMP Join<br/>239.1.2.3"| Router
    
    %% Styling
    classDef senderStyle fill:#e1f5fe,stroke:#01579b,stroke-width:3px
    classDef memberStyle fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px
    classDef nonMemberStyle fill:#ffebee,stroke:#c62828,stroke-width:2px
    classDef routerStyle fill:#fff3e0,stroke:#e65100,stroke-width:3px
    classDef switchStyle fill:#f5f5f5,stroke:#616161,stroke-width:2px
    
    class Sender senderStyle
    class Host1,Host3 memberStyle
    class Host2,Host4,Host5 nonMemberStyle
    class Router routerStyle
    class Switch1,Switch2,Switch3 switchStyle

Internet Group Management Protocol (IGMP)

For multicast to work beyond the local network segment (i.e., across routers), hosts need a way to inform their local routers about multicast groups they are interested in. This is the role of the Internet Group Management Protocol (IGMP).

  • IGMP Versions: IGMPv1, IGMPv2, and IGMPv3 exist, with newer versions adding more features (like source-specific multicast). ESP-IDF’s LWIP stack supports IGMP.
  • How it Works (Simplified):
    1. Joining a Group: When an application on a host wants to receive multicast packets for a group (e.g., 239.1.2.3), it uses a socket option (IP_ADD_MEMBERSHIP). The host’s TCP/IP stack then sends an IGMP “Membership Report” message to its local router(s), indicating its interest in this group.
    2. Router’s Role: Multicast-aware routers listen for these IGMP reports. They build a table of which multicast groups have active members on their connected network segments. When a router receives a multicast packet from an upstream source, it forwards the packet only to those network segments where there are interested members.
    3. Leaving a Group: When an application no longer wants to receive multicast packets, it uses IP_DROP_MEMBERSHIP. The host sends an IGMP “Leave Group” message (in IGMPv2+).
    4. Queries: Routers periodically send IGMP “Membership Query” messages to see if there are still active members for groups on their segments. Hosts respond with Membership Reports if they are still interested.
sequenceDiagram
    participant App as Application (Host)
    participant HostOS as Host OS/LWIP (Host)
    participant Router as Multicast Router

    App->>HostOS: Wants to receive for Group G <br>(e.g., IP_ADD_MEMBERSHIP)
    activate HostOS
    HostOS-->>Router: IGMP Membership Report<br> (Join Group G)
    deactivate HostOS
    activate Router
    Note over Router: Router notes Host's network<br/>has member for Group G
    
    loop Periodically
        Router-->>HostOS: IGMP Membership Query<br> (Who is interested in any group?)
        activate HostOS
        alt Host still interested in Group G
            HostOS-->>Router: IGMP Membership Report <br>(Still in Group G)
        end
        deactivate HostOS
    end
    deactivate Router

    App->>HostOS: No longer wants Group G<br> (e.g., IP_DROP_MEMBERSHIP or close socket)
    activate HostOS
    HostOS-->>Router: IGMP Leave Group <br>(For Group G, IGMPv2+)
    deactivate HostOS
    activate Router
    Note over Router: Router updates membership<br/>for Group G on Host's network
    deactivate Router
    
    

LWIP handles the IGMP signaling automatically when you use the relevant socket options. You generally don’t interact with IGMP packets directly in your application.

Mapping IP Multicast to MAC Multicast

Similar to broadcast, IP multicast packets need to be mapped to a MAC-layer multicast address for transmission on Ethernet-like networks.

For IPv4 multicast, the IANA has reserved a block of MAC addresses: 01:00:5E:00:00:00 to 01:00:5E:7F:FF:FF.

The lower 23 bits of the IP multicast address are mapped to the lower 23 bits of this MAC address range. For example, 224.0.0.1 maps to 01:00:5E:00:00:01.

NICs can be programmed to filter and accept packets destined for specific MAC multicast addresses corresponding to the IP multicast groups the host has joined.

Using Sockets for Multicast (UDP)

Multicast, like broadcast, is typically used with UDP.

  • Sending Multicast Packets:
    1. Create a UDP socket.
    2. Set Time-To-Live (TTL): The TTL field in the IP header controls how many router hops a multicast packet can traverse.
      • TTL = 0: Restricted to the same host (loopback).TTL = 1 (default for many systems): Restricted to the same subnet. Packets will not be forwarded by a router.TTL > 1: Can be forwarded by routers (if they are multicast-enabled and IGMP is working).Use the IP_MULTICAST_TTL socket option:

      unsigned char ttl = 1; // Or higher if needed
      if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
      ESP_LOGE(TAG, "setsockopt(IP_MULTICAST_TTL) failed: %s", strerror(errno));
      }
    3. Specify Outgoing Interface (Optional but Recommended): If the host has multiple network interfaces, you might want to specify which one to use for sending multicast packets. Use IP_MULTICAST_IF.
      struct in_addr local_if;
      local_if.s_addr = inet_addr("YOUR_ESP32_IP_ADDRESS"); // IP of the desired outgoing interface
      if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &local_if, sizeof(local_if)) < 0) {
      ESP_LOGE(TAG, "setsockopt(IP_MULTICAST_IF) failed: %s", strerror(errno));
      }
    4. Control Loopback (Optional): By default, if a host sends a multicast packet and is also a member of that multicast group on the sending interface, it will receive a copy of its own packet. The IP_MULTICAST_LOOP option can disable this.
      unsigned char loopback = 0; // 0 to disable, 1 to enable (default)
      if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loopback, sizeof(loopback)) < 0) {
      ESP_LOGE(TAG, "setsockopt(IP_MULTICAST_LOOP) failed: %s", strerror(errno));
      }
    5. Use sendto() with the destination address set to the desired multicast group IP address and target port.
Socket Option Level Value Type Purpose & Common Values for Senders
IP_MULTICAST_TTL IPPROTO_IP unsigned char (or int) Sets the Time-To-Live for outgoing multicast packets.
  • 0: Restricted to the same host (loopback).
  • 1 (Often Default): Restricted to the same subnet (not forwarded by routers).
  • >1: Allows packets to be forwarded by routers (up to TTL hops).
IP_MULTICAST_IF IPPROTO_IP struct in_addr Specifies the local network interface to use for sending outgoing multicast packets.
  • Provide the IP address of the desired local interface.
  • If not set, the system chooses a default interface based on routing tables.
Important for hosts with multiple network interfaces.
IP_MULTICAST_LOOP IPPROTO_IP unsigned char (or int) Controls whether sent multicast packets are looped back to the sending host if it is also a member of the destination multicast group on the sending interface.
  • 0: Disable loopback.
  • 1 (Often Default): Enable loopback.
  • Receiving Multicast Packets:
    1. Create a UDP socket.
    2. Enable Address Reuse (Optional but often needed): Multiple applications/sockets might want to listen to the same multicast group on the same port. SO_REUSEADDR allows this.
      int reuse = 1;
      if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
      ESP_LOGE(TAG, "setsockopt(SO_REUSEADDR) failed: %s", strerror(errno));
      }
    3. Bind the socket to the multicast port. For the IP address part of the bind, you can use:
      • INADDR_ANY: The socket will receive multicast packets for the joined group(s) arriving on any interface.
      • A specific multicast group address (e.g., 239.1.2.3): This is less common for binding but can sometimes be used. Typically, you bind to INADDR_ANY and then join specific groups.
    4. Join the Multicast Group: This is the crucial step. Use the IP_ADD_MEMBERSHIP socket option. This option requires a struct ip_mreq structure:
      struct ip_mreq {
      struct in_addr imr_multiaddr; /* IP multicast address of group */
      struct in_addr imr_interface; /* IP address of local interface */
      };
      • imr_multiaddr: The multicast group address to join (e.g., inet_addr("239.1.2.3")).imr_interface: The IP address of the local network interface on which to join the group. Use htonl(INADDR_ANY) to let the system choose the default interface, or specify the IP of a particular interface.

      struct ip_mreq imreq;
      imreq.imr_multiaddr.s_addr = inet_addr("YOUR_MULTICAST_GROUP_IP");
      imreq.imr_interface.s_addr = htonl(INADDR_ANY); // Or specific interface IP
      if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq)) < 0) {
      ESP_LOGE(TAG, "setsockopt(IP_ADD_MEMBERSHIP) failed: %s", strerror(errno));
      // This often means the interface doesn't exist or isn't up,
      // or the multicast address is invalid.
      }
      This tells the OS (and LWIP) to start listening for packets to this multicast group and to send IGMP reports.
    5. Use recvfrom() to receive packets.
Step / Option Level (for setsockopt) Value Type / Structure Purpose & Common Usage for Receivers
1. Create Socket N/A (Function call) N/A Create a UDP socket: socket(AF_INET, SOCK_DGRAM, 0).
2. Enable Address Reuse (Optional) SOL_SOCKET int (1 to enable) Set SO_REUSEADDR. Allows multiple sockets (applications) to bind to the same multicast group address and port. Often necessary.
3. Bind Socket N/A (Function call) struct sockaddr_in Bind to the multicast port.
  • IP Address: Typically INADDR_ANY (to receive on any interface).
  • Port: The port on which multicast packets are expected.
bind(sock, (struct sockaddr*)&addr, sizeof(addr))
4. Join Multicast Group IPPROTO_IP struct ip_mreq Set IP_ADD_MEMBERSHIP. This is crucial.
  • imr_multiaddr: The multicast group IP address to join.
  • imr_interface: Local interface IP to join on (or htonl(INADDR_ANY) for default).
This tells the OS/stack to listen for this group and send IGMP reports.
5. Receive Data N/A (Function call) Buffer, struct sockaddr_in (for source info) Use recvfrom() to receive incoming multicast packets.
6. Leave Multicast Group (Optional) IPPROTO_IP struct ip_mreq Set IP_DROP_MEMBERSHIP with the same ip_mreq used for joining. Membership is usually dropped automatically when the socket is closed.
  • Leaving a Multicast Group:When no longer interested, use IP_DROP_MEMBERSHIP with the same struct ip_mreq used for joining.if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imreq, sizeof(imreq)) < 0) { ESP_LOGE(TAG, "setsockopt(IP_DROP_MEMBERSHIP) failed: %s", strerror(errno)); }

Tip: Multicast group membership is associated with a specific socket and interface. If the socket is closed, the membership is typically dropped automatically.

Practical Examples

Ensure your ESP32 is configured with Wi-Fi credentials (e.g., via idf.py menuconfig under Example Connection Configuration) before running these examples. The examples will use UDP.

Example 1: UDP Broadcast Sender and Receiver

This example demonstrates sending a broadcast message and receiving it on another ESP32 (or the same one if loopback is enabled, though broadcast usually implies different devices).

Project Setup:

  1. Create a new ESP-IDF project: idf.py create-project udp_broadcast_example
  2. Navigate into the project: cd udp_broadcast_example
  3. Replace the content of main/main.c.
  4. Configure Wi-Fi in menuconfig.

main/main.c:

C
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#define WIFI_SSID      CONFIG_EXAMPLE_WIFI_SSID
#define WIFI_PASS      CONFIG_EXAMPLE_WIFI_PASSWORD
#define MAX_FAILURES   10

#define BROADCAST_PORT 3333
#define BROADCAST_MESSAGE "ESP32 BROADCAST DISCOVERY"

static const char *TAG = "broadcast_example";

static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1
static int s_retry_num = 0;
static esp_netif_t *s_esp_netif = NULL;


static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < MAX_FAILURES) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        if (s_esp_netif == NULL) {
            s_esp_netif = event->esp_netif;
        }
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    s_esp_netif = esp_netif_create_default_wifi_sta(); // Store default STA netif

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );
    ESP_LOGI(TAG, "wifi_init_sta finished.");

    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s", WIFI_SSID);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID);
    } else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); }
}

void udp_broadcast_receiver_task(void *pvParameters)
{
    char rx_buffer[128];
    int sock;

    ESP_LOGI(TAG, "Waiting for Wi-Fi connection to start UDP broadcast receiver...");
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
    ESP_LOGI(TAG, "Wi-Fi connected. Starting UDP broadcast receiver on port %d", BROADCAST_PORT);

    while (1) {
        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(BROADCAST_PORT);

        sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
        if (sock < 0) {
            ESP_LOGE(TAG, "Receiver: Unable to create socket: errno %d", errno);
            vTaskDelay(pdMS_TO_TICKS(1000));
            continue;
        }
        ESP_LOGI(TAG, "Receiver: Socket created");

        // SO_REUSEADDR allows multiple sockets to bind to the same address and port,
        // which can be useful if restarting the task or for multicast.
        int reuse = 1;
        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
            ESP_LOGE(TAG, "Receiver: setsockopt(SO_REUSEADDR) failed: %s", strerror(errno));
            // Not fatal for this example, but good practice
        }

        int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
        if (err < 0) {
            ESP_LOGE(TAG, "Receiver: Socket unable to bind: errno %d, IPPROTO: %d", errno, IPPROTO_IP);
            close(sock);
            vTaskDelay(pdMS_TO_TICKS(1000));
            continue;
        }
        ESP_LOGI(TAG, "Receiver: Socket bound, waiting for data on port %d", BROADCAST_PORT);

        while (1) {
            struct sockaddr_in source_addr;
            socklen_t socklen = sizeof(source_addr);
            int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);

            if (len < 0) {
                ESP_LOGE(TAG, "Receiver: recvfrom failed: errno %d", errno);
                break; // Break inner loop, re-create socket
            } else {
                rx_buffer[len] = 0; // Null-terminate whatever we received
                ESP_LOGI(TAG, "Receiver: Received %d bytes from %s:", len, inet_ntoa(source_addr.sin_addr));
                ESP_LOGI(TAG, "Receiver: Data: %s", rx_buffer);
            }
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Receiver: Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

void udp_broadcast_sender_task(void *pvParameters)
{
    int sock;

    ESP_LOGI(TAG, "Waiting for Wi-Fi connection to start UDP broadcast sender...");
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
    ESP_LOGI(TAG, "Wi-Fi connected. Starting UDP broadcast sender.");
    
    esp_netif_ip_info_t ip_info;
    ESP_ERROR_CHECK(esp_netif_get_ip_info(s_esp_netif, &ip_info));
    // Construct broadcast address from IP and netmask
    // e.g. IP: 192.168.1.100, Netmask: 255.255.255.0 => Broadcast: 192.168.1.255
    // Or simply use 255.255.255.255 for limited broadcast.
    uint32_t broadcast_ip = (ip_info.ip.addr | (~ip_info.netmask.addr)); // Directed broadcast
    char broadcast_ip_str[16];
    inet_ntoa_r(broadcast_ip, broadcast_ip_str, sizeof(broadcast_ip_str));
    ESP_LOGI(TAG, "Sender: Local IP: " IPSTR ", Netmask: " IPSTR ", Directed Broadcast IP: %s",
             IP2STR(&ip_info.ip), IP2STR(&ip_info.netmask), broadcast_ip_str);
    ESP_LOGI(TAG, "Sender: Using limited broadcast address 255.255.255.255 for wider compatibility.");


    while (1) {
        sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
        if (sock < 0) {
            ESP_LOGE(TAG, "Sender: Unable to create socket: errno %d", errno);
            vTaskDelay(pdMS_TO_TICKS(1000)); // Wait before retrying
            continue;
        }

        int enable_broadcast = 1;
        if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &enable_broadcast, sizeof(enable_broadcast)) < 0) {
            ESP_LOGE(TAG, "Sender: setsockopt(SO_BROADCAST) failed: %s", strerror(errno));
            close(sock);
            vTaskDelay(pdMS_TO_TICKS(1000));
            continue;
        }

        struct sockaddr_in dest_addr;
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(BROADCAST_PORT);
        // Use limited broadcast address
        dest_addr.sin_addr.s_addr = inet_addr("255.255.255.255");
        // Or use directed broadcast address:
        // dest_addr.sin_addr.s_addr = broadcast_ip;


        ESP_LOGI(TAG, "Sender: Sending broadcast message: '%s' to 255.255.255.255:%d", BROADCAST_MESSAGE, BROADCAST_PORT);
        int err = sendto(sock, BROADCAST_MESSAGE, strlen(BROADCAST_MESSAGE), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
        if (err < 0) {
            ESP_LOGE(TAG, "Sender: Error occurred during sending: errno %d", errno);
        } else {
            ESP_LOGI(TAG, "Sender: Message sent successfully.");
        }

        close(sock);
        vTaskDelay(pdMS_TO_TICKS(5000)); // Send every 5 seconds
    }
    vTaskDelete(NULL);
}


void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    wifi_init_sta();

    // You can run both on one ESP32 for testing, or sender on one and receiver on another.
    xTaskCreate(udp_broadcast_receiver_task, "udp_broadcast_rx", 4096, NULL, 5, NULL);
    xTaskCreate(udp_broadcast_sender_task, "udp_broadcast_tx", 4096, NULL, 5, NULL);
}

CMakeLists.txt (in main directory):

Plaintext
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS ".")

Build and Flash Instructions:

  1. Set target: idf.py set-target esp32 (or other variant)
  2. Configure Wi-Fi: idf.py menuconfig
  3. Build: idf.py build
  4. Flash: idf.py -p /dev/ttyUSB0 flash monitor (replace port if needed)

Observe:

The ESP32 will connect to Wi-Fi. The sender task will periodically send a broadcast message. The receiver task (on the same or another ESP32 on the same Wi-Fi network) should log the received messages.

Example 2: UDP Multicast Sender and Receiver

This example demonstrates sending a UDP packet to a multicast group and receiving it by members of that group.

Project Setup:

  1. Create a new ESP-IDF project: idf.py create-project udp_multicast_example
  2. Navigate into the project: cd udp_multicast_example
  3. Replace main/main.c.
  4. Configure Wi-Fi.

main/main.c:

C
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#define WIFI_SSID      CONFIG_EXAMPLE_WIFI_SSID
#define WIFI_PASS      CONFIG_EXAMPLE_WIFI_PASSWORD
#define MAX_FAILURES   10

#define MULTICAST_IPV4_ADDR "232.10.10.10" // Choose a valid multicast address
#define MULTICAST_PORT 4444
#define MULTICAST_TTL  1 // Keep TTL=1 for local network initially
#define MULTICAST_MESSAGE "ESP32 MULTICAST TEST"

static const char *TAG = "multicast_example";

static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1
static int s_retry_num = 0;
static esp_netif_t *s_esp_netif = NULL; // To store the netif handle


// Wi-Fi event handler and init_sta (same as broadcast example)
static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        // esp_wifi_connect() will be called by the event loop system if enabled in sdkconfig
        if (s_retry_num < MAX_FAILURES) {
            //esp_wifi_connect(); // Usually handled by system
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        if (s_esp_netif == NULL) { // Store the netif handle
            s_esp_netif = event->esp_netif;
        }
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    s_esp_netif = esp_netif_create_default_wifi_sta(); // Initialize s_esp_netif

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );
    ESP_LOGI(TAG, "wifi_init_sta finished.");

    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s", WIFI_SSID);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID);
    } else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); }
}


static int create_multicast_ipv4_socket(void)
{
    struct sockaddr_in saddr = { 0 };
    int sock = -1;
    int err = 0;

    sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock < 0) {
        ESP_LOGE(TAG, "Failed to create socket. Error %d", errno);
        return -1;
    }

    // Bind the socket to any address
    saddr.sin_family = PF_INET;
    saddr.sin_port = htons(MULTICAST_PORT);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces for the port
    err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
    if (err < 0) {
        ESP_LOGE(TAG, "Failed to bind socket. Error %d", errno);
        goto err_out;
    }

    // Allow multiple sockets to bind to the same multicast port
    int reuse = 1;
    err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    if (err < 0) {
        ESP_LOGE(TAG, "setsockopt(SO_REUSEADDR) failed. Error %d", errno);
        // Not strictly fatal for receiver, but good practice
    }
    
    // Assign the socket to the ESP_NETIF_DEFAULT_WIFI_STA interface by default
    // This is not strictly necessary for receiving if binding to INADDR_ANY and joining with INADDR_ANY interface
    // but can be useful for sending or more specific scenarios.
    if (s_esp_netif) { // Check if netif handle is available
        err = esp_netif_attach_socket_to_default_if(sock);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "Failed to attach socket to default if. err=0x%x", err);
            goto err_out;
        }
    } else {
        ESP_LOGW(TAG, "s_esp_netif is NULL, cannot attach socket to default if. May work if INADDR_ANY is used for join.");
    }


    // Join the multicast group
    struct ip_mreq imreq = { 0 };
    struct in_addr iaddr = { 0 };

    inet_aton(MULTICAST_IPV4_ADDR, &imreq.imr_multiaddr.s_addr);

    // Configure the interface to join the multicast group on
    // Using INADDR_ANY will use the default interface
    imreq.imr_interface.s_addr = htonl(INADDR_ANY);
    // Or, to bind to a specific interface:
    // esp_netif_ip_info_t ip_info;
    // ESP_ERROR_CHECK(esp_netif_get_ip_info(s_esp_netif, &ip_info));
    // inet_addr_from_ip4addr(&iaddr, &ip_info.ip);
    // imreq.imr_interface.s_addr = iaddr.s_addr;


    ESP_LOGI(TAG, "Joining IPV4 multicast group %s on interface %s...", MULTICAST_IPV4_ADDR, inet_ntoa(imreq.imr_interface));
    err = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(struct ip_mreq));
    if (err < 0) {
        ESP_LOGE(TAG, "Failed to join multicast group. setsockopt IP_ADD_MEMBERSHIP err=%d", errno);
        goto err_out;
    }
    ESP_LOGI(TAG, "Successfully joined multicast group %s", MULTICAST_IPV4_ADDR);

    // Set Multicast TTL (for sender, but often set on same socket if it might also send)
    unsigned char ttl = MULTICAST_TTL;
    err = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
    if (err < 0) {
        ESP_LOGE(TAG, "setsockopt(IP_MULTICAST_TTL) failed. Error %d", errno);
        // Not fatal for receiver
    }

    // Disable multicast loopback (optional)
    // unsigned char loop = 0;
    // err = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
    // if (err < 0) {
    //     ESP_LOGE(TAG, "setsockopt(IP_MULTICAST_LOOP) failed. Error %d", errno);
    // }

    return sock;

err_out:
    close(sock);
    return -1;
}

void udp_multicast_receiver_task(void *pvParameters)
{
    ESP_LOGI(TAG, "Waiting for Wi-Fi connection to start UDP multicast receiver...");
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
    ESP_LOGI(TAG, "Wi-Fi connected. Starting UDP multicast receiver for group %s on port %d", MULTICAST_IPV4_ADDR, MULTICAST_PORT);

    int sock = -1;

    while(1) { // Outer loop to recreate socket if needed
        sock = create_multicast_ipv4_socket();
        if (sock < 0) {
            ESP_LOGE(TAG, "Receiver: Failed to create multicast socket. Retrying in 5s...");
            vTaskDelay(pdMS_TO_TICKS(5000));
            continue;
        }
        ESP_LOGI(TAG, "Receiver: Multicast socket created, waiting for data.");

        char rcv_buffer[128];
        struct sockaddr_in raddr; // Large enough for both IPv4 or IPv6
        socklen_t socklen = sizeof(raddr);

        while (1) { // Inner loop for receiving data
            int len = recvfrom(sock, rcv_buffer, sizeof(rcv_buffer) - 1, 0,
                               (struct sockaddr *)&raddr, &socklen);
            if (len < 0) {
                ESP_LOGE(TAG, "Receiver: Multicast recvfrom failed: errno %d", errno);
                // Potentially close and reopen socket or handle error
                break; // Break inner loop, will re-create socket
            }
            rcv_buffer[len] = 0; // Null-terminate
            ESP_LOGI(TAG, "Receiver: Received %d bytes from %s:%u: '%s'", len,
                     inet_ntoa(raddr.sin_addr), ntohs(raddr.sin_port), rcv_buffer);
        }
        
        ESP_LOGI(TAG, "Receiver: Closing socket and will attempt to recreate.");
        close(sock);
        sock = -1; // Ensure it's marked as closed
        vTaskDelay(pdMS_TO_TICKS(1000)); // Small delay before retrying socket creation
    }
    vTaskDelete(NULL);
}


void udp_multicast_sender_task(void *pvParameters)
{
    ESP_LOGI(TAG, "Waiting for Wi-Fi connection to start UDP multicast sender...");
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
    ESP_LOGI(TAG, "Wi-Fi connected. Starting UDP multicast sender to group %s on port %d", MULTICAST_IPV4_ADDR, MULTICAST_PORT);

    int sock;
    struct sockaddr_in sdestv4 = { 0 };

    sdestv4.sin_family = PF_INET;
    sdestv4.sin_port = htons(MULTICAST_PORT);
    inet_aton(MULTICAST_IPV4_ADDR, &sdestv4.sin_addr.s_addr);

    while(1) {
        sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
        if (sock < 0) {
            ESP_LOGE(TAG, "Sender: Failed to create socket. Error %d", errno);
            vTaskDelay(pdMS_TO_TICKS(1000));
            continue;
        }
        
        // Set the outgoing interface for multicast packets (optional but good practice)
        if (s_esp_netif) { // Check if netif handle is available
            esp_netif_ip_info_t ip_info;
            ESP_ERROR_CHECK(esp_netif_get_ip_info(s_esp_netif, &ip_info));
            struct in_addr local_if;
            inet_addr_from_ip4addr(&local_if, &ip_info.ip);

            if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &local_if, sizeof(local_if)) < 0) {
                ESP_LOGE(TAG, "Sender: setsockopt(IP_MULTICAST_IF) failed: %s", strerror(errno));
                // Non-fatal, system might pick an interface.
            } else {
                ESP_LOGI(TAG, "Sender: Using interface %s for outgoing multicast.", inet_ntoa(local_if));
            }
        } else {
            ESP_LOGW(TAG, "Sender: s_esp_netif is NULL, cannot set IP_MULTICAST_IF. System will choose default.");
        }


        // Set Time To Live (TTL) for multicast packets
        unsigned char ttl = MULTICAST_TTL;
        if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
            ESP_LOGE(TAG, "Sender: setsockopt(IP_MULTICAST_TTL) failed: %s", strerror(errno));
            // Continue anyway, default TTL might be used.
        }

        ESP_LOGI(TAG, "Sender: Sending multicast message: '%s'", MULTICAST_MESSAGE);
        int err = sendto(sock, MULTICAST_MESSAGE, strlen(MULTICAST_MESSAGE), 0,
                         (struct sockaddr *)&sdestv4, sizeof(sdestv4));
        if (err < 0) {
            ESP_LOGE(TAG, "Sender: Error occurred during sending: errno %d: %s", errno, strerror(errno));
        } else {
            ESP_LOGI(TAG, "Sender: Message sent successfully.");
        }

        close(sock);
        vTaskDelay(pdMS_TO_TICKS(5000)); // Send every 5 seconds
    }
    vTaskDelete(NULL);
}


void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    wifi_init_sta();

    xTaskCreate(udp_multicast_receiver_task, "udp_multicast_rx", 4096*2, NULL, 5, NULL); // Increased stack
    xTaskCreate(udp_multicast_sender_task, "udp_multicast_tx", 4096, NULL, 5, NULL);
}

Build and Flash Instructions: (Same as Broadcast Example)

Observe:

The ESP32 will connect to Wi-Fi. The sender task will periodically send a multicast message to the group 232.10.10.10 on port 4444. The receiver task (on the same or another ESP32 on the same Wi-Fi network that has also joined this group) should log the received messages. If you have two ESP32s, flash this code to both. Both will try to send and receive.

Tip: Use a tool like Wireshark on a PC connected to the same network to observe the broadcast and multicast packets. For multicast, you might need to configure your PC’s network interface to join the multicast group as well to see the traffic.

Variant Notes

The fundamental broadcast and multicast mechanisms using the Berkeley Sockets API (provided by LWIP) are consistent across all ESP32 variants (ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2).

  • Network Interface: The examples use Wi-Fi (STA mode). If using Ethernet, the network initialization (wifi_init_sta and esp_netif_create_default_wifi_sta) would need to be replaced with the appropriate Ethernet initialization (e.g., esp_netif_create_default_eth). The socket operations themselves would remain largely the same. The s_esp_netif handle would then point to the Ethernet netif.
  • IGMP Support: LWIP, as used in ESP-IDF, includes IGMP support, which is essential for multicast to function correctly, especially if packets need to traverse routers (though our examples focus on local network with TTL=1). This support is common across variants.
  • Performance & Resources:
    • The number of multicast groups an ESP32 can join or the rate at which it can process broadcast/multicast packets can be influenced by available RAM, CPU speed, and LWIP configuration (e.g., LWIP_NUM_NETIF_CLIENT_DATA).
    • Newer variants (ESP32-S3, C6, H2) with more processing power or optimized peripherals might handle higher traffic loads more effectively.
  • ESP32-H2 (Thread/802.15.4): While these examples focus on IP over Wi-Fi, the ESP32-H2 also supports Thread. Multicast is a key feature of IPv6, which is used in Thread networks (e.g., for service discovery and routing). The concepts are similar, but the specific addresses and underlying mechanisms (e.g., MPL – Multicast Protocol for Low-Power and Lossy Networks) would differ from IPv4 multicast over Wi-Fi.

In summary, the C code for socket operations for broadcast and IPv4 multicast should be highly portable across ESP32 variants using ESP-IDF v5.x. The main differences will lie in the network interface setup.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Forgetting SO_BROADCAST for Senders Broadcast sendto() fails, often with EACCES (Permission denied). Fix: Ensure setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &enable_broadcast, sizeof(enable_broadcast)) is called with enable_broadcast=1 on the sending UDP socket.
Multicast Receiver Not Joining Group Multicast receiver socket is bound, but no multicast packets for the intended group are received. Fix: Crucially, call setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq)). Verify imreq.imr_multiaddr (group IP) and imreq.imr_interface (local interface IP, or htonl(INADDR_ANY)) are correct. Check return value for errors.
Incorrect Multicast TTL Multicast packets not reaching intended recipients beyond the local subnet (if TTL=1) or flooding network (if TTL too high and routers forward). Fix: Set IP_MULTICAST_TTL appropriately. Use TTL=1 for local subnet only. Use higher values (e.g., 16, 32) if packets need to cross routers (requires router support for multicast routing).
Firewall/Router/AP Issues Broadcasts or multicasts work on a very simple local network but fail in more complex setups or between Wi-Fi clients. Fix: Check router settings for multicast routing (IGMP snooping, PIM). Check firewall rules. Disable “AP/client isolation” on Wi-Fi access points if clients on the same AP need to communicate via broadcast/multicast.
Wrong Interface for IP_ADD_MEMBERSHIP or IP_MULTICAST_IF Join fails, or sender uses an unexpected network interface. ESP32 might have multiple interfaces (Wi-Fi STA, AP, Ethernet). Fix: For imr_interface in IP_ADD_MEMBERSHIP, use htonl(INADDR_ANY) to let the system choose, or provide the specific IP of the desired active interface. Same for IP_MULTICAST_IF on the sender.
Binding Receiver to Multicast IP Instead of INADDR_ANY Receiver might not get packets, or behavior is inconsistent. Fix: Typically, bind the multicast receiver socket to INADDR_ANY (or a specific unicast IP of the host interface) and the multicast port. Then, use IP_ADD_MEMBERSHIP to join the group(s).
No SO_REUSEADDR on Multicast Receivers If multiple applications (or restarts of the same app) try to bind to the same port for multicast, subsequent binds may fail with “Address already in use”. Fix: Set SO_REUSEADDR on the multicast receiving socket before binding it. This allows multiple listeners for the same group/port.
Network Not Ready Socket operations fail early in startup (e.g., “Network is unreachable” or join fails). Fix: Ensure the Wi-Fi/Ethernet connection is fully established and an IP address is obtained before attempting broadcast/multicast socket operations. Use event group bits or callbacks to synchronize.

Tip: When debugging, use Wireshark on a PC on the same network to see if the broadcast/multicast packets are actually on the wire and if IGMP messages are being exchanged for multicast.

Exercises

  1. Simple Service Discovery via Broadcast:
    • Sender (Server): Create an ESP32 application that periodically broadcasts a UDP message (e.g., “SERVICE_XYZ_AVAILABLE_AT_IP_192.168.1.100_PORT_8080”) on a specific port. The message should contain its own IP address and the port of a service it offers.
    • Receiver (Client): Create another ESP32 application that listens for these broadcast messages. When a message is received, it parses the IP and port and logs that a service is available.
    • This mimics a very basic service discovery mechanism.
  2. Multicast Group Chat:
    • Modify the UDP multicast example to allow users to send text messages via the serial monitor.
    • When an ESP32 receives a message from the serial input, it sends it to the multicast group.
    • All ESP32s in the group (including the sender, if loopback is enabled or not explicitly disabled) should receive and display the message on their serial monitors, perhaps prefixed by the sender’s IP (if you can determine it or add it to the message).
    • Focus on local network communication (TTL=1).
  3. Controlling Multicast TTL and Loopback:
    • Take the UDP multicast sender/receiver example.
    • Add a mechanism (e.g., a command from serial input or a button press) on the sender to change the IP_MULTICAST_TTL value (e.g., toggle between 1, 2, 5). Observe with Wireshark how the TTL in the IP header changes.
    • Similarly, add a way to toggle IP_MULTICAST_LOOP on the sender. If the sender is also a receiver (running both tasks), observe when it receives its own packets based on the loopback setting.

Summary

  • Broadcast sends data to all hosts on a local network segment (e.g., using IP 255.255.255.255). Requires SO_BROADCAST socket option for senders.
  • Multicast sends data to a specific group of interested hosts (e.g., IP 224.0.0.0239.255.255.255). More efficient and targeted than broadcast for group communication.
  • Receivers must join a multicast group using IP_ADD_MEMBERSHIP socket option. Senders use IP_MULTICAST_TTL to control packet scope and optionally IP_MULTICAST_IF for outgoing interface and IP_MULTICAST_LOOP for loopback control.
  • IGMP is used by hosts and routers to manage multicast group memberships across routed networks. LWIP handles IGMP transparently for applications.
  • Both broadcast and multicast are typically used with UDP.
  • These mechanisms are fundamental for applications like service discovery, data dissemination to multiple clients, and real-time streaming.
  • Proper socket configuration and understanding of network topology are key to successful implementation.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top