IPv6 Multicast on Thread

About

This page explains how to join and leave IPv6 multicast groups on the Thread interface with the OpenThread Arduino library: stack-level APIs, UDP and CoAP wrappers, timing, shutdown, and which shipped examples use each pattern.

Multicast lets one sender reach many receivers on the mesh without listing each node’s unicast address. Application demos in this tree often use realm-local groups (ff03::/16) so every node in the partition can participate.

Send vs receive

Role

Subscribe?

Typical API

Receiver — must accept packets to a group address

Yes

subscribeMulticast(), beginMulticast(), or joinMulticastGroup()

Sender — only transmits to a group address

No

UDP beginPacket(group, port) / endPacket() or CoAP client send

Sending to ff03::abcd does not require joining that group. Only nodes that listen for group traffic must subscribe.

Stack-level API (OThread)

These methods call OpenThread otIp6SubscribeMulticastAddress() / otIp6UnsubscribeMulticastAddress():

bool subscribeMulticast(const IPAddress &group);
bool unsubscribeMulticast(const IPAddress &group);
  • group must be an IPv6 multicast address (first byte 0xFF).

  • Call on the global OThread singleton after OThread.begin().

  • Best practice: subscribe after attach (role Child, Router, or Leader).

  • Pair every subscribe with unsubscribe before shutdown, or use a wrapper that unsubscribes in stop().

See also OpenThread Class (subscribeMulticast / unsubscribeMulticast).

Reference counting

Membership is reference-counted:

First subscribeMulticast(g)

OpenThread joins g; refcount = 1

Another subscribe to the same g

Refcount only

unsubscribeMulticast(g) while refcount > 1

Refcount only

Last unsubscribe for g

OpenThread leaves g

OThreadUDP.beginMulticast() and OThreadCoAPServer.joinMulticastGroup() can share the same group. The refcount table is mutex-protected.

Query membership with getMulticastAddressCount(), getMulticastAddress(), getAllMulticastAddresses(), and clearMulticastAddressCache() when the role or membership changes.

UDP: OThreadUDP

beginMulticast(group, port) (UDP receivers)

OThreadUDP Udp;
const uint8_t groupBytes[16] = {0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xcd};
IPAddress group(IPv6, groupBytes);  // ff03::abcd

Udp.beginMulticast(group, 5051);
// parsePacket() / read() in loop()
Udp.stop();  // unsubscribes and closes the socket

Internally: begin(port) then OThread.subscribeMulticast(group). stop() calls unsubscribeMulticast() for that group. The socket also receives unicast on the same port (for example ACK replies).

See OThreadUDP Class.

begin(port) only (senders and unicast receivers)

Udp.begin(5051);
Udp.beginPacket(multicastGroup, 5051);
Udp.write(...);
Udp.endPacket();

Use on clients that send to a multicast group but do not receive group traffic. Bind a local port when expecting unicast replies.

Manual subscribe + bind

Udp.begin(5050);
OThread.subscribeMulticast(group);
// ...
OThread.unsubscribeMulticast(group);
Udp.stop();

Prefer beginMulticast() unless sharing one subscription across several sockets or toggling membership without closing the socket.

CoAP: OThreadCoAPServer

Servers that receive multicast CoAP requests must join the group after begin():

OThreadCoAPServer.on("Lamp", OT_COAP_METHOD_GET | OT_COAP_METHOD_PUT, onLamp);
OThreadCoAPServer.begin();
OThreadCoAPServer.joinMulticastGroup(LAMP_GROUP);  // e.g. ff03::abcd
  • joinMulticastGroup()OThread.subscribeMulticast().

  • leaveMulticastGroup(group) drops one reference at runtime.

  • stop() leaves all groups recorded by the server.

CoAP multicast behavior (RFC 7252)

  • Clients: use non-confirmable (NON) multicast requests (§8.1); sendNonBlocking() for fire-and-forget.

  • Servers: do not reply to multicast (§8.2); check req.isMulticast() before resp.send().

  • Unicast to one server: use blocking client.PUT(...) / GET(...).

Multicast and CON

CON and multicast cannot be combined. For destinations 0xFF…, the client always sends NON, even if setConfirmable(true) (RFC 7252 §8.1). CON retransmission and setTimeout() tuning do not apply to multicast sends.

Client call

Multicast behavior

Blocking method + setConfirmable(true)

Forced NON; may block for first reply only

sendNonBlocking()

NON, no wait (CoAP_Light_Switch switch)

Confirmed command to one device

Unicast CON to that node’s address

Servers need joinMulticastGroup() to receive group CoAP; group requests are NON — skip resp.send() when req.isMulticast(). See Confirmable vs non-confirmable (CON / NON) in OThreadCoAP APIs for the full CON/NON guide.

OThreadCoAPSecureServer has no joinMulticastGroup(). Use plain OThreadCoAPServer on port 5683 for multicast, secure server on 5684 for commands, or OThread.subscribeMulticast() at the stack level.

See OThreadCoAP APIs.

CLI equivalent

CLI sketches join groups with:

ipmaddr add ff03::abcd

Equivalent to subscribeMulticast() for application traffic. Pair with udp bind in UDP examples.

Multicast scopes

Prefix

Scope

Examples in this library

ff02::

Link-local (one hop)

Rare in demos

ff03::

Realm-local (whole partition)

Native UDP/CoAP light switch (ff03::abcd)

ff05::

Site-local

CLI CoAP lamp/switch (ff05::abcd)

Align network key, channel, group address, and scope when mixing CLI and Native boards. Built-in groups such as ff03::1 appear in address listings; application demos often use a custom suffix (for example ff03::abcd).

Shutdown order

Before OThread.end():

  1. OThreadCoAPServer.stop() / destroy CoAP clients

  2. OThreadUDP.stop() (unsubscribes after beginMulticast())

  3. Direct unsubscribeMulticast() for any remaining groups

  4. OThread.end()

See OpenThread (Shutdown Order) and the Native StackShutdown example.

Examples that use multicast

Native API

Example

Sketch

Role

Mechanism

UDP Light Switch

light

Receiver

OThreadUDP.beginMulticast(ff03::abcd, 5051)

UDP Light Switch

switch

Sender

Udp.begin(5051) + send to ff03::abcd (no subscribe)

CoAP Light Switch

light

Receiver

OThreadCoAPServer.joinMulticastGroup(ff03::abcd)

CoAP Light Switch

switch

Sender

NON multicast PUT via sendNonBlocking()

Native UDP_SensorNetwork uses unicast to the Leader RLOC, not application multicast.

CLI API

Example

Sketch

Role

Mechanism

UDP sensor collector

collector

Receiver

ipmaddr add ff03::abcd + udp bind

UDP sensor node

sensor

Sender

udp send ff03::abcd 5050 ...

CoAP lamp

lamp

Receiver

CLI multicast ff05::abcd

CoAP switch

switch

Sender

CON CoAP PUT to ff05::abcd

Documentation sample

OThreadUDP Class includes a minimal receiver with beginMulticast(ff03::1, 7).

Quick reference

Receiver (UDP)     →  Udp.beginMulticast(group, port)
Receiver (CoAP)    →  CoAPServer.joinMulticastGroup(g)
Sender             →  beginPacket(group, port) — no subscribe
Stack / advanced   →  OThread.subscribeMulticast(g)
Runtime leave      →  leaveMulticastGroup(g) or unsubscribeMulticast(g)
Teardown           →  Udp.stop() / CoAPServer.stop()

See also