From 189dea36a067536503a914f8260862d34b2c890f Mon Sep 17 00:00:00 2001 From: Igor Katson Date: Mon, 16 Sep 2024 17:51:47 +0100 Subject: [PATCH] Windows doesnt have ipv6 netamasks! at least in networkinterfaces package --- crates/upnp-serve/src/ssdp.rs | 10 ++------ crates/upnp/src/lib.rs | 46 ++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/crates/upnp-serve/src/ssdp.rs b/crates/upnp-serve/src/ssdp.rs index 3d0170d..7fa3cfb 100644 --- a/crates/upnp-serve/src/ssdp.rs +++ b/crates/upnp-serve/src/ssdp.rs @@ -6,6 +6,7 @@ use std::{ use anyhow::{bail, Context}; use bstr::BStr; +use librqbit_upnp::ipv6_is_link_local; use network_interface::NetworkInterfaceConfig; use parking_lot::Mutex; use tokio_util::sync::CancellationToken; @@ -21,13 +22,6 @@ const SSDP_MCAST_IPV6_SITE_LOCAL: Ipv6Addr = Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0 const NTS_ALIVE: &str = "ssdp:alive"; const NTS_BYEBYE: &str = "ssdp:byebye"; -fn ipv6_is_link_local(ip: Ipv6Addr) -> bool { - const LL: Ipv6Addr = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0); - const MASK: Ipv6Addr = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0); - - ip.to_bits() & MASK.to_bits() == LL.to_bits() & MASK.to_bits() -} - #[derive(Debug)] pub enum SsdpMessage<'a, 'h> { MSearch(SsdpMSearchRequest<'a>), @@ -286,7 +280,7 @@ USN: {usn}::{device_kind}\r st: &str, addr: SocketAddr, ) -> anyhow::Result { - let local_ip = ::librqbit_upnp::get_local_ip_relative_to(addr.ip())?; + let local_ip = ::librqbit_upnp::get_local_ip_relative_to(addr)?; let location = { let mut loc = self.opts.description_http_location.clone(); let _ = loc.set_ip_host(local_ip); diff --git a/crates/upnp/src/lib.rs b/crates/upnp/src/lib.rs index 44c1f87..7ff09aa 100644 --- a/crates/upnp/src/lib.rs +++ b/crates/upnp/src/lib.rs @@ -30,7 +30,19 @@ pub fn make_ssdp_search_request(kind: &str) -> String { ) } -pub fn get_local_ip_relative_to(local_dest: IpAddr) -> anyhow::Result { +// .to_bits() isn't yet available on min rust version we support (1.75 at the time of writing this) +const fn ip_bits_v6(addr: Ipv6Addr) -> u128 { + u128::from_be_bytes(addr.octets()) +} + +pub fn ipv6_is_link_local(ip: Ipv6Addr) -> bool { + const LL: Ipv6Addr = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0); + const MASK: Ipv6Addr = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0); + + ip_bits_v6(ip) & ip_bits_v6(MASK) == ip_bits_v6(LL) & ip_bits_v6(MASK) +} + +pub fn get_local_ip_relative_to(local_dest: SocketAddr) -> anyhow::Result { fn ip_bits_v4(addr: Ipv4Addr) -> u32 { u32::from_be_bytes(addr.octets()) } @@ -39,10 +51,6 @@ pub fn get_local_ip_relative_to(local_dest: IpAddr) -> anyhow::Result { ip_bits_v4(ip) & ip_bits_v4(mask) } - fn ip_bits_v6(addr: Ipv6Addr) -> u128 { - u128::from_be_bytes(addr.octets()) - } - fn masked_v6(ip: Ipv6Addr, mask: Ipv6Addr) -> u128 { ip_bits_v6(ip) & ip_bits_v6(mask) } @@ -54,16 +62,30 @@ pub fn get_local_ip_relative_to(local_dest: IpAddr) -> anyhow::Result { for addr in i.addr { trace!(%local_dest, nic=i.index, ip=?addr.ip(), nm=?addr.netmask(), "dbg"); match (local_dest, addr.ip(), addr.netmask()) { - (IpAddr::V4(l), IpAddr::V4(a), Some(IpAddr::V4(m))) - if masked_v4(l, m) == masked_v4(a, m) => + // We are connecting to ourselves, return itself. + (l, a, _) if l.ip() == a => return Ok(addr.ip()), + // IPv4 masks match. + (SocketAddr::V4(l), IpAddr::V4(a), Some(IpAddr::V4(m))) + if masked_v4(*l.ip(), m) == masked_v4(a, m) => { return Ok(addr.ip()) } - (IpAddr::V6(l), IpAddr::V6(a), Some(IpAddr::V6(m))) - if masked_v6(l, m) == masked_v6(a, m) => + // Return IPv6 link-local addresses when source is link-local address and there's a scope_id set. + (SocketAddr::V6(l), IpAddr::V6(a), _) + if ipv6_is_link_local(*l.ip()) && l.scope_id() > 0 => + { + if ipv6_is_link_local(a) && l.scope_id() == i.index { + return Ok(addr.ip()); + } + } + // If V6 masks match, return. + (SocketAddr::V6(l), IpAddr::V6(a), Some(IpAddr::V6(m))) + if masked_v6(*l.ip(), m) == masked_v6(a, m) => { return Ok(addr.ip()) } + // For IPv6 fallback to returning a random (first encountered) IPv6 address. + (SocketAddr::V6(_), IpAddr::V6(_), None) => return Ok(addr.ip()), _ => continue, } } @@ -231,9 +253,9 @@ impl UpnpEndpoint { } fn my_local_ip(&self) -> anyhow::Result { - let dest_ip = self.discover_response.received_from.ip(); - let local_ip = get_local_ip_relative_to(dest_ip) - .with_context(|| format!("can't determine local IP relative to {dest_ip}"))?; + let received_from = self.discover_response.received_from; + let local_ip = get_local_ip_relative_to(received_from) + .with_context(|| format!("can't determine local IP relative to {received_from}"))?; Ok(local_ip) }