Skip to content

add UDP multicast support#354

Open
Guest0x0 wants to merge 1 commit intomainfrom
udp-multicast
Open

add UDP multicast support#354
Guest0x0 wants to merge 1 commit intomainfrom
udp-multicast

Conversation

@Guest0x0
Copy link
Copy Markdown
Collaborator

@Guest0x0 Guest0x0 commented May 8, 2026

#348

This PR adds multicast support for moonbitlang/async/socket. It contains the following API change:

  • introduce a new method @socket.UdpServer::multicast(..) for creating shared (i.e. multiple process on the same machine can share the same address + port with this method), multicast-only (i.e. will not receive unicast traffic) UDP server
  • introduce a new method @socket.UdpServer::join_multicast_group{,_v6}(..) for joining IP multicast group from an existing UDP server
  • [breaking] @socket.UdpClient::UdpClient(..) is now async because it may invoke bind on Windows when the destination address is a multicast address
  • introduce a new method @socket.UdpClient::connect(..), for connecting a UDP client to a destination address. Unicast UDP clients are connected automatically on creation, so this is mainly useful for multicast clients. The typical workflow is initiating the client with a multicast destination, send some discovery packet, and wait for server reply. Upon receiving reply from servers, choose a responding server and connect to it, turning the socket into unicast mode with a specific server
  • introduce a new method @socket.UdpClient::recvfrom for the multicast case
  • introduce the methods set_multicast_ttl, set_multicast_loopback, set_multicast_interface and set_multicast_interface_v6 for @socket.UdpServer and @socket.UdpClient for setting various properties concerning multicast

Here are some semantic/implementation details:

  • There is really no such thing as "client" and "server" in the UDP world, especially for the multicast case. The real distinction of @socket.UdpServer and @socket.UdpClient is actually:

    • @socket.UdpServer usually has a fixed/well-known port
    • @socket.UdpClient has a OS-assigned ephemeral port

    For the simple unicast case, the client/server intuition still works. But for the multicast case, "whether the port is well-known" is the correct intuition behind the client/server distinction

  • one of the goals of this PR is to avoid port sharing for unicast, because it is tricky and not portable. Hence there are two ways to create a UDP multicast server in this PR:

    • create a normal unicast UDP server, bound to 0.0.0.0 or [::], and call join_multicast_group later. This way, the server:

      • own the port exclusively
      • can receive both unicast and multicast traffic
      • can join multiple multicast groups with different IPs
    • use @socket.UdpServer::multicast(..) to create a multicast-only server. This way, the server:

      • only receives multicast traffic towards the address specified to @socket.UdpServer::multicast
      • can only join multicast group with that IP (but users can still call join_multicast_group again to join the same IP with different interfaces)

      On Windows, multicast-only behavior is implemented with the SO_REUSE_MULTICASTPORT socket option. On Linux/MacOS, such convenient flag is not available, and this PR relies on the trick of binding the socket to the multicast address. For UDP sockets, the bound address merely serve as a destination address filter for incoming packets, that's why the trick can exclude unicast traffic. The use of this trick has some undesirable consequences though:

      • the "only one multicast IP" restriction
      • IPv6 does not allow binding to a multicast address. So @socket.UdpServer::multicast is IPv4 only and has no IPv6 alternative. This is doable on Windows, but currently I am not aware of a way to do this on Linux/MacOS
  • The IPv6 implementation is currently rather crude. It uses raw interface index everywhere, but we currently do not provide any network interface discovery API, nor ability to specify sin6_scope_id in sendto etc.

@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 257

Coverage increased (+0.005%) to 78.667%

Details

  • Coverage increased (+0.005%) from the base build.
  • Patch coverage: 17 uncovered changes across 2 files (68 of 85 lines covered, 80.0%).
  • 1 coverage regression across 1 file.

Uncovered Changes

File Changed Covered %
src/socket/udp.mbt 69 58 84.06%
src/socket/ffi.mbt 14 8 57.14%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
src/socket/udp.mbt 1 79.35%

Coverage Stats

Coverage Status
Relevant Lines: 4172
Covered Lines: 3282
Line Coverage: 78.67%
Coverage Strength: 59750.04 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants