Skip to content

fix(dds): send unicast SPDP to the configured peer port - #177

Open
youtalk wants to merge 2 commits into
mainfrom
fix/dds-unicast-peer-port
Open

fix(dds): send unicast SPDP to the configured peer port#177
youtalk wants to merge 2 commits into
mainfrom
fix/dds-unicast-peer-port

Conversation

@youtalk

@youtalk youtalk commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Fixes #176.

Root cause

Confirmed against the CycloneDDS source in vendor/cyclonedds, and it is exactly what @peichunhuang-1 diagnosed: DDSPeer.port never reached the <Peer address="..."/> list.

  • q_addrset.c add_addresses_to_addrset_1() — a peer locator with no port makes CycloneDDS patch in the participant unicast discovery port (7400 + 250 * domain + 10) and add one locator per participant index up to MaxAutoParticipantIndex (default 9): 7410, 7412, … 7426 on domain 0. That matches the reported tcpdump exactly.
  • Those ports are dead because the default ParticipantIndex is none (defconfig.c, -2), which ddsi_portmapping.c maps to port 0 — the remote's unicast ports are ephemeral, advertised over SPDP rather than derived. So SPDP left for ports nothing was bound to, discovery never completed, and nothing was reported. The failure is indistinguishable from a firewall block or AP isolation.
  • With an explicit port, maxidx = 0: one locator, the configured port.

Not a regression — the port has been dropped since the first commit.

Two things beyond the report

The RCL path had the same drop. RclTransportSession.swift mapped $0.address too, and since 1.3.0 RCL is the default backend on Apple. Fixing only DDSTransportSession would have left the main path broken.

A second silent-failure defect on the same path. dds_bridge_create_session ignored a failed dds_create_domain and went on to dds_create_participant, which creates an implicit domain on the DEFAULT configuration — multicast SPDP, no <Peers>, no interface pin — while reporting the session as connected. Any discovery config CycloneDDS rejects degraded silently into the same "looks like a firewall" symptom, and it would have hidden precisely the peer misconfiguration this PR makes visible. It now surfaces as DDSError.sessionCreationFailed; DDS_RETCODE_PRECONDITION_NOT_MET ("domain already exists") stays tolerated, since that is the documented process-lifetime limitation where the first session's config wins.

Changes

  • DDSPeer.discoveryAddress — the <Peer address> form: host:port, with a bare IPv6 address bracketed. ddsi_ipaddr_from_string only reads a port off a bracketed IPv6 address, so fe80::1:7400 would otherwise parse as a different address with no port.
  • DDSTransportSession and RclTransportSession both emit it.
  • dds_bridge.c fails loudly on a rejected discovery config.
  • Docs: because the port is now load-bearing, the port: 7400 default is only correct on domain 0 — the DDS article steers to DDSPeer.peer(address:domainId:) for other domains. Parity evidence updated from "bare host" to "host:port".

Verification

  • Failing tests first: both session tests went red with the bare addresses, green after.
  • New coverage: discoveryAddress formatting (IPv4 / bare IPv6 / already-bracketed), port propagation through both transport sessions, port preservation into the generated XML, and a fail-loud regression test.
  • Full suite: 694 XCTest cases + 152 swift-testing tests, exit 0. swift format lint --strict clean.
  • Against the shipping CycloneDDS build, as a paired control: a 192.0.2.10:7400 peer creates the domain — so the new host:port attribute form is accepted — while an unresolvable interface name now throws instead of silently falling back to default discovery.
  • Not run: the LAN end-to-end round-trip (SwiftROS2IntegrationTests) — the ROS 2 host was unreachable at the time of writing. @peichunhuang-1 confirmed the equivalent one-line change on their own setup.

youtalk added 2 commits August 5, 2026 22:51
DDSTransportSession and RclTransportSession mapped only DDSPeer.address
into the CycloneDDS <Peer address="..."/> list, dropping DDSPeer.port.

Without a port, add_addresses_to_addrset_1() patches in the *participant*
unicast discovery port (7400 + 250 * domain + 10) and then adds one
locator per participant index up to MaxAutoParticipantIndex — 7410, 7412,
... 7426 on domain 0. A remote running the default ParticipantIndex
("none") binds ephemeral unicast ports, so nothing listens there: SPDP
went to dead ports, discovery never completed, and no error was raised.
The failure is indistinguishable from a firewall block or AP isolation,
which is what it gets misdiagnosed as.

Both call sites now emit DDSPeer.discoveryAddress — host:port, with a
bare IPv6 address bracketed because ddsi_ipaddr_from_string only reads a
port off a bracketed IPv6 address (fe80::1:7400 would otherwise parse as
a different address entirely).

Because the port is now load-bearing, the port: 7400 default is only
correct on domain 0; the docs point at DDSPeer.peer(address:domainId:)
for other domains.

Reported and diagnosed by @peichunhuang-1.

Fixes #176

Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
dds_bridge_create_session ignored a failed dds_create_domain and carried
on to dds_create_participant, which creates an *implicit* domain on the
DEFAULT configuration: multicast SPDP, no <Peers>, no network-interface
pin. The session then reported itself connected while discovering
nothing — the same "looks like a firewall block" symptom that made #176
so hard to pin down, and it hides exactly the peer misconfiguration the
previous commit makes visible.

The rejection now surfaces as DDSError.sessionCreationFailed.
DDS_RETCODE_PRECONDITION_NOT_MET stays tolerated: it means the domain
already exists (another session, or rmw), which is the documented
process-lifetime limitation where the first session's config wins.

Verified against the shipping CycloneDDS build: a peer of
192.0.2.10:7400 creates the domain (so the new host:port attribute form
is accepted), while an unresolvable network interface now throws instead
of silently falling back to default discovery.

Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
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.

DDSTransportSession: unicast peer port silently dropped, causing SPDP to guess wrong ports

1 participant