Do not announce IPv6 unicast locators over an IPv4-only transport - #441
Merged
Merged
Conversation
`get_local_unicast_locators_inner` turned every local interface address
into an announced unicast locator, IPv6 included. But our unicast
transport is IPv4-only: `UDPListener::new_listening_socket` and the
`UDPSender` unicast socket are both created with `Domain::IPV4`. (The
multicast path already reflects this -- `get_local_multicast_ip_addrs_inner`
filters on `IpAddr::is_ipv4`.)
So every announced IPv6 locator is an address we cannot receive on and
cannot send to. Peers -- including our own participant, which discovers
itself over loopback multicast -- dutifully send RTPS metatraffic there,
and each datagram fails with EAFNOSUPPORT and logs a WARN:
raw_send: Unicast to [fe80::d494:8fff:fe08:3ce3]:7420 :
Os { code: 97, ... "Address family not supported by protocol" } len=64
On a host with several IPv6-capable interfaces (docker0, br-*, veth*,
wlan, ...) this is thousands of warnings per second, which drowns the
log of any application using RustDDS. Link-local addresses are doubly
useless here, since a `Locator` carries no IPv6 scope id, so even an
IPv6-capable socket could not use them.
Two changes:
* `get_local_unicast_locators_inner` now filters to IPv4, so we only
advertise addresses the transport can actually serve. This also stops
us handing unreachable locators to other vendors' implementations.
* `UDPSender::raw_send` checks that the chosen socket's address family
matches the destination before calling `send_to`, and drops the
datagram with a `trace!` otherwise. A remote peer may legitimately
announce locators we cannot reach; that is not a warning-worthy local
fault, and it must not cost a syscall + WARN per datagram.
Behaviour is otherwise unchanged: IPv4 locators, multicast enumeration
and the `only_networks` filter all work exactly as before.
Co-authored-by: Copilot & Claude Opus 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is ai-generated pull request (Copilot & Claude Opus 5) to fix warning spam I had. So check this PR with care. I do not understand how every linux pc is not affected by it? So please test it on your machine if it is reproducible, because it's hard to believe issue this direct could be present on every linux machine. The ai's description below:
Do not announce IPv6 unicast locators over an IPv4-only transport
Summary
A RustDDS participant announces a unicast locator for every local interface
address, IPv6 included, but the unicast transport is IPv4-only. Peers — including
the participant itself, which discovers itself over loopback multicast — then send
RTPS metatraffic to those IPv6 locators through an IPv4 socket. Every such datagram
fails with
EAFNOSUPPORTand logs aWARN, thousands of times per second.The bug
src/network/util.rs,get_local_unicast_locators_inner:Every address is turned into a locator. But the sockets underneath are IPv4-only:
src/network/udp_listener.rs,new_listening_socket:Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?— the unicastlistener binds
0.0.0.0, so we never receive on an IPv6 address.src/network/udp_sender.rs,new_with_networks: theunicast_socketislikewise
Domain::IPV4, bound to0.0.0.0, so we can never send to one either.The multicast path already encodes this restriction —
get_local_multicast_ip_addrs_innerends with
.filter(IpAddr::is_ipv4)— so the unicast path is simply inconsistentwith the rest of the module.
Consequently the announced IPv6 locators are addresses we can neither receive on
nor send to. Discovery hands them to
RtpsReaderProxy/RtpsWriterProxy, thetransmit path calls
UDPSender::send_to_locator, which routes a non-multicastdestination to
SocketId::Unicast, andraw_sendproduces:There is one such line per datagram, per unreachable locator.
Why this is a bug and not cosmetic
It is unconditional and self-inflicted. No IPv6 configuration, no remote
peer and no unusual setup is required. A single process creating two
participants on a stock Linux box reproduces it, because a participant
discovers itself over loopback multicast and reads back its own locator list.
Any interface with a link-local address — which is every real interface on a
modern Linux host — contributes one unreachable locator.
The log volume makes RustDDS applications unusable at
WARN. On my host(7 IPv6-capable interfaces: wlan, two docker bridges, two veths, two USB
ethernets) the MWE below emits 14 181 warnings in 10 seconds.
WARNisthe level applications leave on in production; this drowns everything else.
The application cannot fix it — there is no public API to exclude the IPv6
addresses, since
only_networksis an allow-list of addresses that muststill resolve through the same code path.
We advertise unreachable addresses to other implementations. SPDP/SEDP
locator lists are consumed by other vendors too. Announcing addresses we
cannot serve is wrong on the wire, wastes peers' send attempts, and can slow
endpoint matching for anyone who tries the locators in order.
Link-local IPv6 could never work here anyway.
Locatorcarries no IPv6scope id, so
fe80::…is unusable even for an IPv6-capable socket — thekernel cannot know which interface to use. These locators are not "IPv6
support waiting to be enabled"; they are not addressable at all.
The warning is misattributed.
EAFNOSUPPORThere is not a transientnetwork fault worth warning about; it is the guaranteed outcome of asking an
IPv4 socket for an IPv6 destination. It also costs a failing syscall per
datagram.
MWE
Save the script below as
examples/ipv6_locator_mwe/main.rsand run:(No special networking setup is needed — any host with at least one
IPv6-capable interface (i.e. essentially any Linux/macOS machine, since every
real NIC gets a link-local address) reproduces it. The
statusevents=offfilter suppresses an unrelated, pre-existing warningStatusChannelSender cannot send new status changes, channel is full.)Result on
master(0.14.2), 10 s run:With this PR these warning dissappear.
The fix
Two independent changes, both small:
src/network/util.rs— do not announce what we cannot serve.get_local_unicast_locators_innernow filters to IPv4, matching theIPv4-only unicast listener/sender and mirroring what
get_local_multicast_ip_addrs_inneralready does. This removes the cause.src/network/udp_sender.rs— do not warn about a destination we structurallycannot reach. New
socket_can_reachchecks the chosen socket's addressfamily against the destination;
raw_senddrops a mismatched datagram with atrace!instead of attempting a syscall that can only fail. This covers theremaining legitimate case: a remote peer announcing IPv6 locators, which is
not a local fault and must not cost a
WARNper datagram.SocketId::Unicastis IPv4-only; a multicast socket is checked against the family of the
interface address it was bound to, so the existing IPv6 multicast branch in
new_with_networkskeeps working if multicast enumeration is ever widened.Behaviour is otherwise unchanged: IPv4 locators, multicast enumeration and the
only_networksfilter all behave exactly as before.Tests
Two new unit tests:
network::util::tests::unicast_locators_exclude_ipv6— a synthetic interfacelist containing a link-local and a ULA IPv6 address yields only the IPv4
locator. Uses the existing injectable
_innerhelper, so it is deterministicand independent of the host's interfaces.
network::udp_sender::tests::unicast_socket_rejects_ipv6_destination— theunicast socket reports it cannot reach an IPv6 destination, and
raw_sendreturns
SendOutcome::Droppedfor it.Full suite passes:
cargo test→ 692 + 2 + 58 tests, 0 failures.cargo +nightly fmtandcargo +nightly clippy --tests --examplesare clean.Notes for reviewers
IPv6 (or dual-stack) unicast listener + sender plus scope-id-carrying
locators. The
socket_can_reachcheck added here is the correct guard inthat world too — it is not a workaround that would need removing.
ros2-client0.10.1: a single ROS 2 node withno peers and no traffic produced 6 749 of these warnings in 15 seconds.
DomainParticipantswith no user endpoints emit ~250
StatusChannelSender ... channel is fullwarnings in 10 s, from the
DataWriterStatus/DataReaderStatuschannels ofthe builtin discovery endpoints (
sync_status_channel(4)insrc/dds/pubsub.rs). Applications have no way to drain those. Happy to open aseparate issue if that is useful — it is not touched here.