Skip to content

Bind default sources to an ephemeral port so they don't take receivers' unicast - #84

Open
stewartadam wants to merge 1 commit into
mainfrom
claude/project-thread-0t11p6-3-source-ephemeral-port
Open

stewartadam wants to merge 1 commit into
mainfrom
claude/project-thread-0t11p6-3-source-ephemeral-port

Conversation

@stewartadam

@stewartadam stewartadam commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Problem

SacnSource::new_v4, new_v6, with_cid_v4 and with_cid_v6 bound the source socket to 0.0.0.0:5568 (or [::]:5568) with SO_REUSEPORT. The receiver binds the same port with the same option. On Linux that puts both sockets in one reuseport group, and the kernel spreads incoming unicast datagrams across the group by hashing the sender's address and port. The source never reads its socket, so any unicast sACN that hashes to it is silently lost. Multicast is unaffected because it's delivered to every socket.

I confirmed this on Linux before changing anything. With a receiver on 127.0.0.1:5568 and an idle default source in the same process, the receiver got 25 of 40 unicast packets sent from fresh ephemeral ports. With no source running, it got 40 of 40.

Fix

  • The default constructors now bind port 0, so the OS picks an ephemeral port. Sources only send, and E1.31 sets the destination port, not the source port.
  • with_ip docs now warn against binding ACN_SDT_MULTICAST_PORT when a receiver may share the host.

Callers that pass an explicit address to with_ip or with_cid_ip keep exactly what they asked for.

Test

Added test_default_source_does_not_steal_unicast to tests/src_tests.rs. It sends 20 unicast packets from fresh sockets to a receiver while a default source is running. It fails on main (14 of 20 received in one run) and passes with this change. All of tests/src_tests.rs passes.

SacnSource::new_v4/new_v6 bound 0.0.0.0:5568 with SO_REUSEPORT, the same
port and option the receiver uses. On Linux that puts both sockets in one
reuseport group and unicast datagrams are spread between them by source
port, so a receiver on the same host lost part of its unicast sACN to the
source's socket, which is never read. In a local test 15 of 40 unicast
packets went missing.

The default constructors now bind port 0. with_ip documents the pitfall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzaQVPkrkczgX2g7wGkip5
@stewartadam stewartadam self-assigned this Nov 12, 2026
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