Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ edition = "2021"
# the ROOT manifest (`[workspace.package].version`), so it MUST be set here for a
# release to fire (§3.6). The library crates (dig-node-core/dig-runtime/dig-wallet)
# keep their own independent versions — only the released binary tracks the workspace version.
version = "0.75.2"
version = "0.75.3"

# Release hardening, matching digstore: keep integer-overflow checks ON in release.
# The node parses untrusted serialized input and does offset/length arithmetic over
Expand Down
37 changes: 34 additions & 3 deletions crates/dig-node-core/src/seams/dig_peer/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ pub fn dual_stack_listen_addr(port: u16) -> SocketAddr {

/// Whether an [`Ipv6Addr`] is a *global-unicast* address we can advertise to peers: not loopback, not
/// unspecified, not link-local (`fe80::/10`), not unique-local (`fc00::/7`, i.e. `fc00::` / `fd00::`),
/// and not an IPv4-mapped address. Such an address is (best-effort) routable, so it belongs at the
/// front of the advertised candidate list.
/// and not an IPv4-mapped OR IPv4-compatible address. Such an address is (best-effort) routable, so it
/// belongs at the front of the advertised candidate list.
pub fn is_advertisable_ipv6(ip: &Ipv6Addr) -> bool {
if ip.is_loopback() || ip.is_unspecified() || ip.to_ipv4_mapped().is_some() {
if ip.is_loopback() || ip.is_unspecified() || ip.to_ipv4().is_some() {
return false;
}
let seg0 = ip.segments()[0];
Expand Down Expand Up @@ -544,6 +544,37 @@ mod tests {
assert!(is_advertisable_ipv6(&"2606:4700::1".parse().unwrap()));
}

#[test]
fn ipv4_compatible_is_not_advertisable() {
// IPv4-compatible address (::1.2.3.4 = ::0102:0304) is an IPv4 address in disguise.
// It MUST be rejected as non-routable IPv6, just like IPv4-mapped addresses.
let compat_addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x0102, 0x0304);
assert!(
!is_advertisable_ipv6(&compat_addr),
"IPv4-compatible address must not be advertisable"
);
}

#[test]
fn ipv4_mapped_is_still_not_advertisable() {
// Regression guard: IPv4-mapped addresses (::ffff:a.b.c.d) must continue to be rejected.
let mapped_addr: Ipv6Addr = "::ffff:1.2.3.4".parse().unwrap();
assert!(
!is_advertisable_ipv6(&mapped_addr),
"IPv4-mapped address must not be advertisable"
);
}

#[test]
fn a_real_global_unicast_ipv6_is_advertisable() {
// Guard against over-rejecting: genuine global-unicast addresses must remain advertisable.
let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
assert!(
is_advertisable_ipv6(&global),
"global-unicast address must be advertisable"
);
}

#[test]
fn advertisable_ipv4_rejects_loopback_linklocal_broadcast() {
assert!(!is_advertisable_ipv4(&Ipv4Addr::LOCALHOST));
Expand Down
Loading