2

So far I use multicast with ipv4 and it works; all involved computers run linux. I listen on two machines and send on one of those two (in a separate terminal). In the below example 'Hello 1' is received on the sending machine (strawberry) and on the remote machine (ero).

ero:~$ sudo ip addr add 224.4.19.42 dev enp4s0 autojoin
ero:~$ netcat -l -k -u -p 9988

strawberry:~ $ sudo ip addr add 224.4.19.42 dev wlan0 autojoin
strawberry:~ $ netcat -l -k -u -p 9988

strawberry:~ $ echo "Hello 1" | netcat -s 192.168.178.109 -w 0 -u 224.4.19.42 9988

With ipv6 it works as long as only remote machines listen; 'Hello 2' in the below example is received by ero. Once the sender (strawberry) has also joined the multicast group, neither the sender (strawberry) nor the remote machine (ero) receives 'Hello 3':

ero:~$ sudo ip addr add ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 dev enp4s0 autojoin
ero:~$ netcat -l -k -u -p 9988

strawberry:~ $ echo "Hello 2" | netcat -w 0 -s 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:76d0 -u ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 9988

strawberry:~ $ sudo ip addr add ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 dev wlan0 autojoin
strawberry:~ $ netcat -l -k -u -p 9988

strawberry:~ $ echo "Hello 3" | netcat -w 0 -s 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:76d0 -u ff05:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:4141 9988

Maybe of interest: when I do not provide a sender address, i.e., no -s option, then the ipv4 example shows the same behaviour as ipv6: message only received as long as strawberry has not joined the multicast group. Thus I tried different sending addresses with ipv6: the global address shown in the example (2001:...), a unique local address (ULA; fd00:...) and a link-local address (LLA; fe80:...). Neither helps.

Any hints what I am doing wrong?

10
  • You should not add an IP multicast address to an interface. You should have an application using the correct API to join multicast groups and/or enable the socket option to received looped back multicast traffic. netcat can do neither of these. socat can do both for IPv4 and can also do this for IPv6 by using an hand crafted parameter for the correct IPv6 multicast setsockopt syscall since it appears it doesn't support it with a "native" option.
    – A.B
    Commented Feb 11, 2023 at 20:08
  • @A.B. Great, thanks for pointing this out. It works well for ipv4 (receiving end: socat UDP4-RECV:9988,ip-add-membership=224.42.42.42:192.168.178.109 - ; still using netcat to send). I need to do some more reading for the hand crafted parameter for the correct IPv6 multicast setsockopt syscall .
    – Al_
    Commented Feb 12, 2023 at 15:08
  • Here's an example in an answer of mine for the IPv6 part: unix.stackexchange.com/questions/656582/…
    – A.B
    Commented Feb 12, 2023 at 16:27
  • Great. Your example from the other thread works perfectly. Many thanks.For other readers: needs a recent socat version else option setsockopt-listen is missing (1.7.3.2 on debian buster is too old, but 1.7.4.1 works and all dependencies are fulfilled on an updated buster, so the socat binary can be simply copied). And also site-local multicast addresses [ff05:...] require to add an interface, i.e., i2 or i3 or so.
    – Al_
    Commented Feb 14, 2023 at 17:13
  • @A.B If you copy your comment to an answer, I am more than happy to give you credit by marking the question as solved.
    – Al_
    Commented Feb 18, 2023 at 8:46

1 Answer 1

2

Introduction

For a host system, while sending to a multicast IP address is quite similar to sending to an unicast address, receiving multicast is different and uses additional APIs: an host doesn't assign a multicast address to an interface, instead it joins multicast addresses of interest to receive select multicast traffic when an application (using a socket) on it requests it. This is described in RFC 1112. The RFC's JoinHostGroup and LeaveHostGroup functions are transposed on the BSD socket API used by most *nix by the use of setsockopt(2) options to join and leave multicast groups. IPv6 follows this just as IPv4 but there are differences (including in routing behavior). POSIX even describes in a few places how should be implemented the IPv6 API (it doesn't appear to describe IPv4's multicast so much, perhaps because it's a bit more fragmented among OSes and thus not worth it).

On Linux, the main socket options used are IP_MULTICAST_IF plus IP_ADD_MEMBERSHIP for IPv4 and IPV6_MULTICAST_IF plus IPV6_ADD_MEMBERSHIP (with an alias of IPV6_JOIN_GROUP to follow POSIX) for IPv6. Also of interest to OP's question, but enabled by default: IP_MULTICAST_LOOP and IPV6_MULTICAST_LOOP.

Knowing what happens on the wire to have this work inside a network (IGMP for IPv4 or MLD for IPv6...) or across networks (for example using PIM-SM) isn't needed at the application level (but could be needed to troubleshoot problems, especially those involving switches and multicast snooping).

The remaining answer expects that no special tinkering to network configuration was done on the systems: no multicast address added to any interface and no autojoin option. Note also that 224.4.19.42 belongs to an AD-HOC Block II block assigned to the London Stock exchange. Private Organization-Local Scope blocks can be picked within 239.0.0.0/8.

netcat

netcat (all of its variants) doesn't handle multicast. So while by tweaking the network settings (autojoin is normally intended to be used along configurations involving tunnels) OP managed to use netcat with IPv4 multicast addresses but not IPv6 anyway, any application actually using multicast follows the standards and uses the additional APIs for proper multicast support. A diagnostic tool such as netcat is used to replace a more complex software, but this replacement should be able to do the same: netcat won't.

socat

IPv4

socat has (almost) complete IPv4 multicast support.

The way to send to such multicast group is by stating through which multicast-enabled interface it should be sent through (actually socat only appears to support the address variant of this API, so an interface can't be provided when using socat while the API supports it, unless using dalan raw setsockopt socat option, see below) and optionally by stating if multicast loopback is required (it's enabled by default so useless here, so I'll put it in only one example):

On strawberry, if a default route exists or at least a route to multicast 224.4.19.42 using the expected interface is present, then simply this is enough (the first command doesn't require any multicast-related feature, so that's the only case that nc can also be used for):

echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988
echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,ip-multicast-loop=1

else the interface has to be specified by an address on it:

echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,ip-multicast-if=192.168.178.109

Using the documented dalan format one can use arbitrary socket options in socat using its setsockopt option for the unsupported part of the API described in Introduction paragraph. It is OS and can be architecture dependent. Here it will be about Linux (>= 3.5) on amd64 (x86_64) architecture.

SOL_IP = 0
IP_MULTICAST_IF = 32

expected alternate structure for an in_mreq variant of the API: one IPv4 multicast address (4 bytes in hex for the IPv4 address in big endian format, introduced by a leading x) plus one local address (4 more bytes). For an in_mreqn variant: same plus also one index of integer size introduced by a leading i. Not all parameters must be filled in. Working examples (using JSON format and the jq command to compute the interface index from wlan0):

echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,setsockopt=0:32:x00000000xc0a8b26d
echo "Hello 1" | socat -u - UDP4-DATAGRAM:224.4.19.42:9988,setsockopt=0:32:x00000000x00000000i$(ip -j link show enp4s0 | jq '.[].ifindex')

If using ip-multicast-loop=0, then strawberry will not receive its own emitted multicast traffic.

The receiving part has to use IP_ADD_MEMBERSHIP to specify the multicast IP address group to join, and optionally a local address or an interface if one doesn't want to rely on the routing stack to pick the correct choice (again one of these options is needed if there is no correct route to the multicast address, for example if there is no default route or if the default is wrong). Stating this local address shouldn't be needed either when the socket is bound to an address other than INADDR_ANY since then the OS will follow this choice. The receiving part can use UDP4-RECV (to merge what is received) or UDP4-RECVFROM (to split on each datagram) usually along a fork option. For example:

If there is a route (incl. default) for 224.4.19.42, then on both hosts:

socat -u UDP4-RECV:9988,ip-add-membership=224.4.19.42:0.0.0.0 -

Else (here choosing the interface name syntax),

  • for strawberry:

    socat -u UDP4-RECV:9988,ip-add-membership=224.4.19.42:wlan0 -
    
  • for ero:

    socat -u UDP4-RECV:9988,ip-add-membership=224.4.19.42:enp4s0 -
    

IPv6

It appears IPv6 support was partially implemented: similar to ip-add-membership for IPv4, the equivalent option ipv6-add-membership does exist in sources and works but is not documented anywhere. All tests were made with socat 7.4.1 so showing this release. The option exists:

#ifdef IPV6_JOIN_GROUP
        IF_IP6    ("ipv6-add-membership",       &opt_ipv6_join_group)
#endif

or an alias:

#ifdef IPV6_JOIN_GROUP
        IF_IP6    ("ipv6-join-group",   &opt_ipv6_join_group)
#endif

with the format declaration for using setsockopt(2) later:

#ifdef IPV6_JOIN_GROUP
const struct optdesc opt_ipv6_join_group = { "ipv6-join-group", "join-group", OPT_IPV6_JOIN_GROUP, GROUP_SOCK_IP6, PH_PASTSOCKET, TYPE_IP_MREQN, OFUNC_SOCKOPT, SOL_IPV6, IPV6_JOIN_GROUP };
#endif

So basic listening to multicast IPv6 is actually supported (else this would require a dalan setsockopt-listen socat option which requires a recent version). Choosing to send not using the default routing stack's choice isn't natively possible because there is no ipv6-multicast-if option (yet?). It's still available with the dalan format. The structure used for IPV6_MULTICAST_IF requires only an interface index. Using IPV6_MULTICAST_IF is probably always required if the IPv6 multicast scope is below site-local (eg: ff01::/16 or ff02::/16) but is often required for other cases too (see below). Likewise, the parameter ipv6-multicast-loop has not been implemented so also requires a dalan format setsockopt option.

IPv6 routing behavior differs slightly from IPv4 here. It might not follow the default route and might have undefined behavior on a system using multiple interface where routes have not be explicitly defined, depending on the order the interfaces are (re)configured: the routing table could switch from:

# ip -6 route show table all type multicast
multicast ff00::/8 dev wlan0 table local proto kernel metric 256 pref medium
multicast ff00::/8 dev dummy0 table local proto kernel metric 256 pref medium

to

# ip -6 route show table all type multicast
multicast ff00::/8 dev dummy0 table local proto kernel metric 256 pref medium
multicast ff00::/8 dev wlan0 table local proto kernel metric 256 pref medium

just because an interface went down then up, changing the default interface to be used. Running any kind of container, VM or dynamic interface could lead to this.

In the end, just sending blindly trusting the system's routing stack choice (or if routes are known to be configured correctly) on strawberry (mind the quotes to avoid the shell interpreting brackets needed for IPv6):

echo "Hello 1" | socat -u - UDP6-DATAGRAM:'[ff05::4141]':9988

Specifying an interface to send through and explicitly stating to use multicast loopback requires dalan format setsockopt option:

SOL_IPV6 = 41
IPV6_MULTICAST_LOOP = 19
IPV6_MULTICAST_IF = 17
IPV6_MULTICAST_LOOP's boolean: actually integer (hence the letter i used in dalan data format)
IPV6_MULTICAST_IF's index: integer

echo "Hello 1" | socat -u - UDP6-DATAGRAM:'[ff05::4141]':9988,setsockopt=41:19:i1,setsockopt=41:17:i$(ip -j link show wlan0 | jq '.[].ifindex')
  • receiving on strawberry:

    socat -u UDP6-RECV:9988,ipv6-add-membership='[ff05::4141]':wlan0 -
    
  • likewise receiving on ero:

    socat -u UDP6-RECV:9988,ipv6-add-membership='[ff05::4141]':enp4s0 -
    

IPv6 multicast communication between an arbitrary number of peers is possible. Here's an example, while disabling receiving its own looped back multicast traffic and specifying the interface:

  • strawberry:

    socat UDP6-DATAGRAM:'[ff05::4141]':9988,bind=:9988,ipv6-add-membership='[ff05::4141]':wlan0,setsockopt=41:19:i0,setsockopt=41:17:i$(ip -j link show wlan0 | jq '.[].ifindex') -
    
  • ero (same, only the interface name chances):

    socat UDP6-DATAGRAM:'[ff05::4141]':9988,bind=:9988,ipv6-add-membership='[ff05::4141]':enp4s0,setsockopt=41:19:i0,setsockopt=41:17:i$(ip -j link show enp4s0 | jq '.[].ifindex') -
    
  • additional systems can run the same command too, only the interface name will possibly differ.

Each socat will send data (typed on the terminal) to all other multicast peers and will also read back traffic sent from them (but not from itself).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .