Multicast

Multicast refers to sending messages to interested receivers. Compared with the two "extremes" of unicast and broadcast addressing schemes (either one or all), multicast provides a compromise solution. As the name implies, multicast mainly emphasises the concept of groups, that is, a host can send a message to a group address, and all hosts that join this group can receive the message. This is somewhat like subnet-directed broadcast, but more flexible than it, because a host can join or leave a certain group at any time, thus reducing the burden on the local network and hosts.

Internet Group Management Protocol (IGMP) is a protocol responsible for managing IP multicast members, which is used to establish and maintain multicast group membership relationship between an IP host and its directly adjacent multicast Wi-Fi routers. For multicast, Wi-Fi routers need to support the IGMP protocol.

1. Multicast addresses

The destination addresses of multicast messages use a class D IP address. The first byte starts with binary numbers 1110, and it ranges from 224.0.0.0 to 239.255.255.255. Since the multicast IP address identifies a group of hosts, the multicast IP address can only be used as the destination address, not the source address, which is always a unicast address.

A multicast group is a group identified by a specific multicast address. When members inside or outside the group send a message to this multicast address, group members identified by the multicast address can receive the message. Multicast groups can be permanent or temporary. Among multicast addresses, multicast addresses officially assigned are called permanent multicast groups, while those that are neither reserved nor permanent are called temporary multicast groups. The numbers of hosts in permanent and temporary multicast groups are dynamic and may even be zero.

Multicast addresses are classified as follows:

  • 224.0.0.0 ~ 224.0.0.255: Reserved multicast addresses (permanent multicast groups). The address 224.0.0.0 is not allocated, and the others are used for routing protocols.
  • 224.0.1.0 ~ 224.0.1.255: Public multicast addresses, which can be used on the Internet.
  • 224.0.2.0 ~ 238.255.255.255: Multicast addresses available to users (temporary multicast groups), which are valid throughout the network.
  • 239.0.0.0 ~ 239.255.255.255: Multicast addresses for local management, which are valid only within specific local ranges.

2. Implementing a multicast sender using socket

📝 Source code

For the source code of the function esp_join_multicast_group(), please refer to book-esp32c3-iot-projects/test_case/multicast_discovery.

The implementation of multicast sending is more complex than that of broadcast sending. Multicast sending requires setting the sending interface of the multicast packets. If you need to receive packets from a certain multicast group, you also need to join the multicast group. The function esp_join_multicast_group() implements the setting of the multicast group sending interface and the function of joining the multicast group. The function esp_send_multicast() implements the creation, binding, configuration of destination address and port of regular UDP sockets, and sending and receiving functions. In addition, TTL settings are also added to ensure that the multicast group can only be performed in the LAN of this route. The code is as below:

#define MULTICAST_IPV4_ADDR "232.10.11.12"
int esp_join_multicast_group(int sockfd)
{
	struct ip_mreq imreq = { 0 };
	struct in_addr iaddr = { 0 };
	int err = 0;

	//Configure sending interface of multicast group
	esp_netif_ip_info_t ip_info = { 0 };
	err = esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info);
    if (err ! = ESP_OK) {
        ESP_LOGE(TAG, "Failed to get IP address info. Error 0x%x", err);
        goto err;
    }
    inet_addr_from_ip4addr(&iaddr, &ip_info.ip);
    err = setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &iaddr, sizeof(struct in_addr));
    if (err < 0) {
		ESP_LOGE(TAG, "Failed to set IP_MULTICAST_IF. Error %d", errno);
		goto err;
    }

    //Configure the address of monitoring multicast group
    inet_aton(MULTICAST_IPV4_ADDR, &imreq.imr_multiaddr.s_addr);

    //Configure the socket to join the multicast group
    err = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(struct ip_mreq));
    if (err < 0) {
		ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
    }
err:
    return err;
}

esp_err_t esp_send_multicast(void)
{
    esp_err_t err = ESP_FAIL;
    struct sockaddr_in saddr = {0};
    struct sockaddr_in from_addr = {0};
    socklen_t from_addr_len = sizeof(struct sockaddr_in);
    char udp_recv_buf[64 + 1] = {0};

    //Create an IPv4 UDP socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        ESP_LOGE(TAG, "Create UDP socket fail");`
        return err;
    }

    //Bind socket
	saddr.sin_family = PF_INET;
	saddr.sin_port = htons(3333);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
	if (ret < 0) {
        ESP_LOGE(TAG, "Failed to bind socket. Error %d", errno);
        goto exit;
	}
    
    //Set multicast TTL to 1, limiting the multicast packet to one route
	uint8_t ttl = 1;
    ret = setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(uint8_t));
    if (ret < 0) {
        ESP_LOGE(TAG, "Failed to set IP_MULTICAST_TTL. Error %d", errno);
        goto exit;
	}

	//Join the multicast group
    ret = esp_join_multicast_group(sockfd);
    if (ret < 0) {
        ESP_LOGE(TAG, "Failed to join multicast group");
        goto exit;
    }

    //Set multicast destination address and port
    struct sockaddr_in dest_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(3333),
    };
    inet_aton(MULTICAST_IPV4_ADDR, &dest_addr.sin_addr.s_addr);
    char *multicast_msg_buf = "Are you Espressif IOT Smart Light";

    //Call sendto() to send multicast data
    ret = sendto(sockfd, multicast_msg_buf, strlen(multicast_msg_buf), 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
    if (ret < 0) {
        ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
    } else {
        ESP_LOGI(TAG, "Message sent successfully");
        ret = recvfrom(sockfd, udp_recv_buf, sizeof(udp_recv_buf) - 1, 0,
                        (struct sockaddr *)&from_addr,
                        (socklen_t *)&from_addr_len);
        if (ret > 0) {
            ESP_LOGI(TAG, "Receive udp unicast from %s:%d, data is %s",
                    inet_ntoa(((struct sockaddr_in *)&from_addr)->sin_addr),
                    ntohs(((struct sockaddr_in *)&from_addr)->sin_port),
                    udp_recv_buf);
            err = ESP_OK;
        }
    }
exit:
    close(sockfd);
    return err;
}

3. Implementing a multicast receiver using socket

📝 Source code

For the source code of the function esp_recv_multicast(), please refer to book-esp32c3-iot-projects/test_case/multicast_discovery.

Implementing a multicast receiver is similar to implementing a multicast sender, which requires specifying the interface of multicast packets and the multicast group to be joined. The function esp_recv_multicast() implements the creation, binding, configuration of destination address and port of regular UDP sockets, and sending and receiving functions. In addition, since multicast needs to be sent in this example, Time To Live (TTL) is set. The code is as below:

esp_err_t esp_recv_multicast(void)
{
    esp_err_t err = ESP_FAIL;
    struct sockaddr_in saddr = {0};
    struct sockaddr_in from_addr = {0};
    socklen_t from_addr_len = sizeof(struct sockaddr_in);
    char udp_server_buf[64+1] = {0};
    char *udp_server_send_buf = "ESP32-C3 Smart Light https 443";

    //Create an IPv4 UDP socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        ESP_LOGE(TAG, "Create UDP socket fail");
        return err;
    }

    //Bind socket
    saddr.sin_family = PF_INET;
    saddr.sin_port = htons(3333);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
    if (ret < 0) {
        ESP_LOGE(TAG, "Failed to bind socket. Error %d", errno);
        goto exit;
    }

    //Set multicast TTL to 1, limiting the multicast packet to one route
    uint8_t ttl = 1;
    ret = setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(uint8_t));
    if (ret < 0) {
        ESP_LOGE(TAG, "Failed to set IP_MULTICAST_TTL. Error %d", errno);
        goto exit;
    }

    //Join the multicast group
    ret = esp_join_multicast_group(sockfd);
    if (ret < 0) {
        ESP_LOGE(TAG, "Failed to join multicast group");
        goto exit;
    }

    //Call recvfrom() to receive multicast data
    while (1) {
        ret = recvfrom(sockfd, udp_server_buf, sizeof(udp_server_buf) - 1, 0,
                        (struct sockaddr *)&from_addr,
                        (socklen_t *)&from_addr_len);
        if (ret > 0) {
            ESP_LOGI(TAG, "Receive udp multicast from %s:%d, data is %s",
                    inet_ntoa (((struct sockaddr_in *)&from_addr)->sin_addr),
                    ntohs(((struct sockaddr_in *)& from_addr)->sin_port),
                    udp_server_buf);
            //Upon reception of multicast request, send data communication port of peer through unicast
            if (!strcmp(udp_server_buf, "Are you Espressif IOT Smart Light")) {
                ret = sendto(sockfd, udp_server_send_buf, strlen(udp_server_send_buf),
                            0, (struct sockaddr *)&from_addr, from_addr_len);
                if (ret < 0) {
                    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                } else {
                    ESP_LOGI(TAG, "Message sent successfully");
                }
            }
        }
    }
exit:
    close(sockfd);
    return err;
}

4. Running result

Add the sender and receiver code to the Wi-Fi Station example to ensure they are connected to the same Wi-Fi router. The log of multicast sending is as follows:

I (752) wifi :mode : sta (c4:4f:33:24:65:f1) 
I (752) wifi:enable tsf 
I (752) wifi station: wifi init sta finished. 
I (772) wifi:new: <6,0>, old: <1,0>, ap: <255,255>, sta: <6,0>, prof:1 
I (772) wifi:state: init -> auth (b0) 
I (792) wifi:state: auth -> assoc (0)
I (802) wifi:state: assoc -> run (10) 
I (822) wifi:connected with myssid, aid = 2, channel 6, BW20, bssid = 34:29:12:43:c5:40 
I (822) wifi:security: WPA2-PSK, phy: bgn, rssi: -17 
I (822) wifi: pm start, type: 1 
I (882) wifi:AP's beacon interval = 102400 us, DTIM period = 1 
I (1542) esp_netif_handlers: sta ip: 192.168.3.5, mask: 255.255.255.0, gw: 192.168.3.1 
I (1542) wifi station: got ip:192.168.3.5
I (1542) wifi station: connected to ap SSID: myssid password: 12345678 
I (1552) wifi station: Message sent successfully 
I (1632) wifi station: Receive udp unicast from 192.168.3.80:3333, data is ESP32-C3 Smart Light https 443

The log of multicast reception is as follows:

I (806) wifi:state: init -> auth (b0)
I (816) wifi:state: auth -> assoc (0) 
I (836) wifi:state: assoc -> run (10)
I (966) wifi:connected with myssid, aid = 1, channel 6, BW20, bssid = 34:29:12:43:c5:40
I (966) wifi:security: WPA2-PSKI phy: bgn, rssi: -29
I (976) wifi:pm start, type: 1 
I (1066) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (2056) esp_netif_handlers: sta ip: 192.168.3.80, mask: 255.255.255.0, gw: 192.168.3.1 
I (2056) wifi station: got ip:192.168.3.80 
I (2056) wifi station: connected to ap SSID: myssid password: 12345678 
W (18476) wifi: <ba-add>idx:0 (ifx:0, 34:29:12:43:c5:40), tid:0, ssn:4, winSize: 64
W (23706) wifi: <ba-add>idx:1 (ifx:0, 34:29:12:43:c5:40), tid:5, ssn:0, winSize: 64
I (23706) wifi station: Receive udp multicast from 192.168.3.5:3333, data is Are you Espressif IOT Smart Light

Similar to broadcasting logs, the sender sends packets of specific data, and the receiver informs the sender of the application protocol and port number of data communication.