Skip to content

fix(rtps): same-network locator selection uses the interface's prefix length, not a fixed /24 - #867

Open
finger563 wants to merge 2 commits into
mainfrom
fix/rtps-subnet-netmask
Open

finger563 wants to merge 2 commits into
mainfrom
fix/rtps-subnet-netmask

Conversation

@finger563

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #866 / #865. The same-subnet locator preference compared the first three octets, i.e. it assumed every network is a /24. The issue's own host had its Wi-Fi on 10.5.15.154/17: a board on that network with a different third octet would have fallen back to the first announced locator, the original bug in a narrower form.

  • rtps core: isSameSubnetAddress() / isSameSubnet() take a prefix length (default 24, the previous behaviour; clamped to 32, 0 matches everything); prefixLengthFromNetmask() converts a dotted-quad mask (non-contiguous masks are rejected). The participant carries m_localPrefixLength, set through DomainConfig::local_prefix_length and Participant::reuse(), and every selection site fix(rtps): prefer the peer's unicast locator on our subnet; correct the GAP length #866 added (SEDP topic data, the CycloneDDS default-locator fallback, the SPDP locator lists) plus the existing metatraffic selection use it. The SPDP multicast lists no longer go through the on-network replacement, which never applied to them.
  • Facade: RtpsParticipant::Config::interface_netmask ("255.255.128.0" or "17"). Empty means: on the host, the netmask of the interface that owns interface_address (read from getifaddrs / GetAdaptersAddresses, for an auto-detected and for an explicitly configured address alike); on ESP targets, a /24 with a debug log. An invalid value fails start().
  • Ethernet::netmask() and ethernet_netmask() on the four Ethernet boards, so an application can pass the netif's mask next to its IP; the rtps and esp32-p4-function-ev-board examples do.
  • Python: RtpsParticipant.Config(interface_netmask=...) (keyword, appended last so positional callers are unaffected).
  • Test: pc/tests/rtps_locator_subnet gains the prefix-length checks (explicit /16, /17, /32, /0, clamp, netmask conversion) and /17 SEDP + SPDP selection cases, alongside the existing /24 ones.

No wire-format change.

Test plan

  • Host: rtps_locator_subnet (new checks), rtps_golden, rtps_sedp_dedicated_locator, rtps_pubsub pass against the installed library
  • components/rtps/example builds for esp32 (IDF 6.1, component manager off)
  • components/esp32-p4-function-ev-board/example builds for esp32p4 (IDF 6.1)
  • Host library with ESPP_BUILD_PYTHON=ON builds (binding compiles)
  • CI (interop matrix, static analysis, examples)

🤖 Generated with Claude Code

https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU

… length, not a fixed /24

The locator preference from #866 compared the first three octets, so a
peer on a network wider than a /24 (the issue's host had its Wi-Fi on
10.5.15.154/17) with a different third octet fell back to the first
announced locator, the original #865 bug in a narrower form.

- rtps core: isSameSubnetAddress() / isSameSubnet() take a prefix
  length (default 24, the previous behaviour); prefixLengthFromNetmask()
  converts a dotted-quad mask. The participant carries the local prefix
  length (DomainConfig::local_prefix_length -> Participant::reuse()), and
  every selection site uses it: SEDP topic data, the CycloneDDS
  default-locator fallback, the SPDP locator lists and the metatraffic
  selection. The SPDP multicast lists skip the on-network replacement,
  which never applied to them.
- Facade: RtpsParticipant::Config::interface_netmask ("255.255.128.0"
  or "17"). Empty: the netmask of the interface that owns
  interface_address on the host (getifaddrs / GetAdaptersAddresses,
  auto-detected or configured address alike), a /24 on ESP targets.
- Ethernet::netmask() and ethernet_netmask() on the Ethernet boards so
  applications can pass the netif's mask; the rtps and
  esp32-p4-function-ev-board examples do.
- Python: RtpsParticipant.Config(interface_netmask=...).
- pc/tests/rtps_locator_subnet: prefix-length checks and /17 SEDP + SPDP
  selection cases next to the existing /24 ones.

No wire-format change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
Copilot AI balanced review requested due to automatic review settings November 1, 2026 03:54
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

3 open issues
What changed in this PR

Updates RTPS “same subnet” locator selection to use the interface’s actual prefix length (CIDR) instead of assuming a fixed /24, improving correctness on networks like /17 and other non-/24 subnets.

Changes:

  • Add prefix-length aware subnet checks (isSameSubnetAddress, isSameSubnet) and netmask→prefix conversion.
  • Thread the local prefix length through participant/domain/discovery selection paths (SEDP/SPDP/metatraffic).
  • Expose interface_netmask in the facade (C++/Python) and surface Ethernet netmask on ESP boards + update examples/tests.
File Description
pc/​tests/​rtps_locator_subnet.cpp Expands tests to cover prefix-length behavior (/16, /17, /32, /0) and netmask conversion.
lib/​python_bindings/​rtps_bindings.cpp Adds interface_netmask to Python config/init and forwards into Rtps::Config.
components/​rtps/​src/​rtps_participant.cpp Implements interface netmask parsing + host netmask detection and passes computed prefix length into Domain config.
components/​rtps/​src/​entities/​Participant.cpp Stores and reuses local prefix length in Participant instances.
components/​rtps/​src/​entities/​Domain.cpp Passes domain-configured prefix length into participant reuse.
components/​rtps/​src/​discovery/​TopicData.cpp Makes SEDP unicast locator preference prefix-length aware.
components/​rtps/​src/​discovery/​SPDPAgent.cpp Uses participant prefix length when selecting on-subnet metatraffic locator.
components/​rtps/​src/​discovery/​SEDPAgent.cpp Uses participant prefix length when selecting on-subnet endpoint locators and parsing TopicData.
components/​rtps/​src/​discovery/​ParticipantProxyData.cpp Uses prefix length in list replacement logic; prevents multicast lists from using on-network replacement.
components/​rtps/​include/​rtps_participant.hpp Adds interface_netmask to facade config and updates interface resolution API.
components/​rtps/​include/​rtps/​entities/​Participant.hpp Adds participant field for local prefix length and updates reuse signature.
components/​rtps/​include/​rtps/​entities/​Domain.hpp Adds local_prefix_length to DomainConfig.
components/​rtps/​include/​rtps/​discovery/​TopicData.hpp Updates readFromBuffer API to accept optional prefix length.
components/​rtps/​include/​rtps/​discovery/​ParticipantProxyData.hpp Documents and updates locator-list reading API to include prefix length.
components/​rtps/​include/​rtps/​common/​Locator.hpp Introduces DEFAULT_SUBNET_PREFIX_LENGTH, prefix-aware subnet checks, and netmask→prefix conversion.
components/​rtps/​example/​main/​rtps_example.cpp Passes Ethernet netmask into RtpsParticipant config on ESP example.
components/​rtps/​README.md Documents interface_netmask behavior and recommendations for ESP targets.
components/​ethernet/​src/​ethernet.cpp Tracks netmask alongside IP and resets it on link/IP changes.
components/​ethernet/​include/​ethernet.hpp Exposes Ethernet::netmask() and stores netmask atomically.
components/​esp32-p4-module-dev-kit/​include/​esp32-p4-module-dev-kit.hpp Adds ethernet_netmask() helper.
components/​esp32-p4-function-ev-board/​include/​esp32-p4-function-ev-board.hpp Adds ethernet_netmask() helper.
components/​esp32-p4-function-ev-board/​example/​main/​esp32_p4_function_ev_board_example.cpp Fetches netmask and passes it to RTPS participant config.
components/​esp32-p4-eth/​include/​esp32-p4-eth.hpp Adds ethernet_netmask() helper.
components/​esp32-ethernet-kit/​include/​esp32-ethernet-kit.hpp Adds ethernet_netmask() helper.

Review effort: Lite

Comment on lines +56 to +61
bool parse_netmask(const std::string &text, uint8_t &prefix_length) {
unsigned a = 0, b = 0, c = 0, d = 0;
if (std::sscanf(text.c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) == 4) {
if (a > 255 || b > 255 || c > 255 || d > 255) {
return false;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9fbb686: the dotted-quad parse now uses a %c sentinel and only accepts exactly four conversions, so "255.255.255.0foo" is rejected (and "17" already used the same sentinel).

logger_.debug("No netmask known for {}; assuming a /{} network (set interface_netmask)", addr,
prefix_length);
}
logger_.info("Interface {}/{}", addr, prefix_length);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9fbb686: the auto-detected address is logged at INFO only where auto-detection happens (host, as before this PR); the per-start "Interface a.b.c.d/n" line is now DEBUG, so an explicitly configured address adds no INFO output.

Comment thread pc/tests/rtps_locator_subnet.cpp Outdated

bool check_spdp() {
// 3. Fill each list with off-subnet locators, then announce the on-subnet one.
bool isSameSameSubnetClamp() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isSameSubnetPrefixClamp() in 9fbb686, with a comment on what it checks.

- parse_netmask(): a dotted quad with trailing characters
  ("255.255.255.0foo") is rejected (a %c sentinel must not match).
- resolve_interface(): the auto-detected address is logged at INFO
  where it is found (host blocks only, as before); the per-start
  "Interface a.b.c.d/n" line is DEBUG. ESP targets share the
  empty-address error with the host instead of an early return, which
  also removes the duplicate / always-false auto_detect conditions
  cppcheck reported on the ESP configuration.
- Windows adapter cursor is a pointer to const.
- Test helper renamed isSameSubnetPrefixClamp().

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU

This branch has not been deployed

No deployments
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