Skip to content

fix(mdns): skip address translation when observed source is IPv6 link… - #6479

Merged
mergify[bot] merged 5 commits into
libp2p:masterfrom
prateushsharma:fix/mdns-ipv6-link-local-translation
Sep 10, 2026
Merged

fix(mdns): skip address translation when observed source is IPv6 link…#6479
mergify[bot] merged 5 commits into
libp2p:masterfrom
prateushsharma:fix/mdns-ipv6-link-local-translation

Conversation

@prateushsharma

Copy link
Copy Markdown
Contributor

fix(mdns): skip address translation when observed source is IPv6 link-local

Problem

On an IPv6-only LAN, extract_discovered passes every announced address through _address_translation(address, &observed). The observed multiaddr is built from the mDNS packet's UDP source address, which on an IPv6 LAN is always the sender's link-local address (fe80::/10). This causes the peer's routable ULA address (fd12::1) to be silently replaced with the link-local source, and every subsequent QUIC dial fails because fe80:: without a zone ID is unroutable.

The IPv4 path is unaffected because private IPv4 addresses pass through _address_translation differently.

Closes #6474.

Fix

Added observed_is_link_local — a small predicate using Ipv6Addr::is_unicast_link_local from std. When it returns true, extract_discovered returns the announced address as-is and skips _address_translation entirely. No NAT is present on a LAN so translation is both unnecessary and incorrect here.

IPv4 and global/ULA IPv6 observed addresses are unaffected.

Tests

  • test_observed_is_link_local — unit test for the predicate (fe80::, fd12::, 192.168.x.x)
  • test_extract_discovered_ipv6_lan_preserves_ula_address — regression: simulates a packet arriving from fe80::abcd:ef01 while the peer announces fd12::1/udp/4001/quic-v1, asserts the ULA address survives in the output unchanged

Testing on real hardware

@ohrensessel has a live IPv6-only ULA setup and has offered to validate this PR against it.

prateushsharma and others added 2 commits June 9, 2026 03:35
…-local

On an IPv6-only LAN, mDNS multicast packets always originate from the
sender's link-local address (fe80::/10). _address_translation was
replacing the peer's announced routable ULA/global address with this
link-local source, making every QUIC dial fail because fe80:: without
a zone ID is not routable.

Fix: when the observed source is fe80::/10, skip translation entirely
and return the announced address unchanged. No NAT is present on a LAN
so translation is both unnecessary and harmful here. IPv4 and global
IPv6 paths are unaffected.

Fixes libp2p#6474

@jxs jxs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi, and thanks for this!
Can you cargo fmt your changes please?

@jxs jxs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cargo fm'ted and introduced the CHANGELOG.md entry

@jxs jxs added the send-it label Sep 10, 2026
@mergify mergify Bot added the queued label Sep 10, 2026
@mergify

mergify Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-09-10 11:14 UTC · Rule: default · triggered by rule Add send-it labeled PRs to merge queue
  • Checks skipped · PR is already up-to-date
  • Merged2026-09-10 11:15 UTC · at 036a5e9bf44f6221e569972fc4571b96328574d2 · squash

This pull request spent 1 minute 6 seconds in the queue, including 7 seconds running CI.

Required conditions to merge
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Check rustdoc intra-doc links
    • check-neutral = Check rustdoc intra-doc links
    • check-skipped = Check rustdoc intra-doc links
  • any of [🛡 GitHub branch protection]:
    • check-success = rustfmt
    • check-neutral = rustfmt
    • check-skipped = rustfmt
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-metrics
    • check-neutral = Test libp2p-metrics
    • check-skipped = Test libp2p-metrics
  • any of [🛡 GitHub branch protection]:
    • check-success = Compile on x86_64-apple-darwin
    • check-neutral = Compile on x86_64-apple-darwin
    • check-skipped = Compile on x86_64-apple-darwin
  • any of [🛡 GitHub branch protection]:
    • check-success = Compile on x86_64-pc-windows-msvc
    • check-neutral = Compile on x86_64-pc-windows-msvc
    • check-skipped = Compile on x86_64-pc-windows-msvc
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-yamux
    • check-neutral = Test libp2p-yamux
    • check-skipped = Test libp2p-yamux
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-swarm
    • check-neutral = Test libp2p-swarm
    • check-skipped = Test libp2p-swarm
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-mdns
    • check-neutral = Test libp2p-mdns
    • check-skipped = Test libp2p-mdns
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-relay
    • check-neutral = Test libp2p-relay
    • check-skipped = Test libp2p-relay
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-uds
    • check-neutral = Test libp2p-uds
    • check-skipped = Test libp2p-uds
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-rendezvous
    • check-neutral = Test libp2p-rendezvous
    • check-skipped = Test libp2p-rendezvous
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p
    • check-neutral = Test libp2p
    • check-skipped = Test libp2p
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-noise
    • check-neutral = Test libp2p-noise
    • check-skipped = Test libp2p-noise
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-gossipsub
    • check-neutral = Test libp2p-gossipsub
    • check-skipped = Test libp2p-gossipsub
  • any of [🛡 GitHub branch protection]:
    • check-success = Test rw-stream-sink
    • check-neutral = Test rw-stream-sink
    • check-skipped = Test rw-stream-sink
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-dns
    • check-neutral = Test libp2p-dns
    • check-skipped = Test libp2p-dns
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-floodsub
    • check-neutral = Test libp2p-floodsub
    • check-skipped = Test libp2p-floodsub
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-swarm-derive
    • check-neutral = Test libp2p-swarm-derive
    • check-skipped = Test libp2p-swarm-derive
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-pnet
    • check-neutral = Test libp2p-pnet
    • check-skipped = Test libp2p-pnet
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-tls
    • check-neutral = Test libp2p-tls
    • check-skipped = Test libp2p-tls
  • any of [🛡 GitHub branch protection]:
    • check-success = Test multistream-select
    • check-neutral = Test multistream-select
    • check-skipped = Test multistream-select
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-kad
    • check-neutral = Test libp2p-kad
    • check-skipped = Test libp2p-kad
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-core
    • check-neutral = Test libp2p-core
    • check-skipped = Test libp2p-core
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-tcp
    • check-neutral = Test libp2p-tcp
    • check-skipped = Test libp2p-tcp
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-websocket
    • check-neutral = Test libp2p-websocket
    • check-skipped = Test libp2p-websocket
  • any of [🛡 GitHub branch protection]:
    • check-success = IPFS Integration tests
    • check-neutral = IPFS Integration tests
    • check-skipped = IPFS Integration tests
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-identify
    • check-neutral = Test libp2p-identify
    • check-skipped = Test libp2p-identify
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-dcutr
    • check-neutral = Test libp2p-dcutr
    • check-skipped = Test libp2p-dcutr
  • any of [🛡 GitHub branch protection]:
    • check-success = Compile on wasm32-unknown-emscripten
    • check-neutral = Compile on wasm32-unknown-emscripten
    • check-skipped = Compile on wasm32-unknown-emscripten
  • any of [🛡 GitHub branch protection]:
    • check-success = Compile with select features (mdns tcp dns tokio)
    • check-neutral = Compile with select features (mdns tcp dns tokio)
    • check-skipped = Compile with select features (mdns tcp dns tokio)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-request-response
    • check-neutral = Test libp2p-request-response
    • check-skipped = Test libp2p-request-response
  • any of [🛡 GitHub branch protection]:
    • check-success = Compile on wasm32-unknown-unknown
    • check-neutral = Compile on wasm32-unknown-unknown
    • check-skipped = Compile on wasm32-unknown-unknown
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-mplex
    • check-neutral = Test libp2p-mplex
    • check-skipped = Test libp2p-mplex
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-ping
    • check-neutral = Test libp2p-ping
    • check-skipped = Test libp2p-ping
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-plaintext
    • check-neutral = Test libp2p-plaintext
    • check-skipped = Test libp2p-plaintext
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-autonat
    • check-neutral = Test libp2p-autonat
    • check-skipped = Test libp2p-autonat
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-quic
    • check-neutral = Test libp2p-quic
    • check-skipped = Test libp2p-quic
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-webrtc
    • check-neutral = Test libp2p-webrtc
    • check-skipped = Test libp2p-webrtc
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-identity
    • check-neutral = Test libp2p-identity
    • check-skipped = Test libp2p-identity
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-allow-block-list
    • check-neutral = Test libp2p-allow-block-list
    • check-skipped = Test libp2p-allow-block-list
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-swarm-test
    • check-neutral = Test libp2p-swarm-test
    • check-skipped = Test libp2p-swarm-test
  • any of [🛡 GitHub branch protection]:
    • check-success = Check for changes in proto files
    • check-neutral = Check for changes in proto files
    • check-skipped = Check for changes in proto files
  • any of [🛡 GitHub branch protection]:
    • check-success = Ensure that `Cargo.lock` is up-to-date
    • check-neutral = Ensure that `Cargo.lock` is up-to-date
    • check-skipped = Ensure that `Cargo.lock` is up-to-date
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-connection-limits
    • check-neutral = Test libp2p-connection-limits
    • check-skipped = Test libp2p-connection-limits
  • any of [🛡 GitHub branch protection]:
    • check-success = examples
    • check-neutral = examples
    • check-skipped = examples
  • any of [🛡 GitHub branch protection]:
    • check-success = Run all WASM tests
    • check-neutral = Run all WASM tests
    • check-skipped = Run all WASM tests
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-memory-connection-limits
    • check-neutral = Test libp2p-memory-connection-limits
    • check-skipped = Test libp2p-memory-connection-limits
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-webtransport-websys
    • check-neutral = Test libp2p-webtransport-websys
    • check-skipped = Test libp2p-webtransport-websys
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-perf
    • check-neutral = Test libp2p-perf
    • check-skipped = Test libp2p-perf
  • any of [🛡 GitHub branch protection]:
    • check-success = Compile with MSRV
    • check-neutral = Compile with MSRV
    • check-skipped = Compile with MSRV
  • any of [🛡 GitHub branch protection]:
    • check-success = manifest_lint
    • check-neutral = manifest_lint
    • check-skipped = manifest_lint
  • any of [🛡 GitHub branch protection]:
    • check-success = Test libp2p-server
    • check-neutral = Test libp2p-server
    • check-skipped = Test libp2p-server
  • any of [🛡 GitHub branch protection]:
    • check-success = Compile on wasm32-wasip1
    • check-neutral = Compile on wasm32-wasip1
    • check-skipped = Compile on wasm32-wasip1

@mergify
mergify Bot merged commit 036a5e9 into libp2p:master Sep 10, 2026
67 of 70 checks passed
@mergify mergify Bot removed the queued label Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mdns: IPv6 peer addresses silently replaced with link-local source on LAN discovery

2 participants