From 20c5c2caefc7ceee457af78163f278456e6989a5 Mon Sep 17 00:00:00 2001 From: Robert Klotzner Date: Fri, 11 Sep 2020 23:26:32 +0200 Subject: [PATCH 1/6] Join multicast group on all interfaces Fix issue #1518. --- protocols/mdns/Cargo.toml | 1 + protocols/mdns/src/service.rs | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 9ddee2842b6..6b94f7b7c3f 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -25,6 +25,7 @@ smallvec = "1.0" tokio = { version = "0.2", default-features = false, features = ["udp"], optional = true } void = "1.0" wasm-timer = "0.2.4" +pnet = "0.26.0" [dev-dependencies] get_if_addrs = "0.5.3" diff --git a/protocols/mdns/src/service.rs b/protocols/mdns/src/service.rs index b3913812c54..320329981ae 100644 --- a/protocols/mdns/src/service.rs +++ b/protocols/mdns/src/service.rs @@ -23,6 +23,8 @@ use dns_parser::{Packet, RData}; use either::Either::{Left, Right}; use futures::{future, prelude::*}; use libp2p_core::{multiaddr::{Multiaddr, Protocol}, PeerId}; +use pnet::datalink; +use pnet::ipnetwork::IpNetwork; use std::{convert::TryFrom as _, fmt, io, net::Ipv4Addr, net::SocketAddr, str, time::{Duration, Instant}}; use wasm_timer::Interval; use lazy_static::lazy_static; @@ -165,8 +167,10 @@ impl $service_name { socket.set_multicast_loop_v4(true)?; socket.set_multicast_ttl_v4(255)?; - // TODO: correct interfaces? - socket.join_multicast_v4(From::from([224, 0, 0, 251]), Ipv4Addr::UNSPECIFIED)?; + // Join multicast on all avaliable interfaces: + for addr in get_interface_addresses() { + socket.join_multicast_v4(From::from([224, 0, 0, 251]), addr)?; + } Ok($service_name { socket, @@ -278,6 +282,23 @@ impl $service_name { } } + +/// Get IPv4 addresses of all external network interfaces. +fn get_interface_addresses() -> impl Iterator { + datalink::interfaces() + .into_iter() + .filter(|i| i.is_up() && !i.is_loopback()) + .filter_map(|i| { + i.ips + .into_iter() + .filter_map(|n| match n { + IpNetwork::V4(n4) => Some(n4.ip()), + _ => None, + }) + .next() // Simply get the first valid IPv4. + }) +} + impl fmt::Debug for $service_name { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("$service_name") From cf1fed7b95c97ecb102e8096f48054193282238a Mon Sep 17 00:00:00 2001 From: Robert Klotzner Date: Sat, 12 Sep 2020 08:50:20 +0200 Subject: [PATCH 2/6] Move get_interfaces_addresses out of macro This avoids duplicate symbols when all features are enabled. --- protocols/mdns/src/service.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/protocols/mdns/src/service.rs b/protocols/mdns/src/service.rs index 320329981ae..99b4b5e8d31 100644 --- a/protocols/mdns/src/service.rs +++ b/protocols/mdns/src/service.rs @@ -282,6 +282,16 @@ impl $service_name { } } +impl fmt::Debug for $service_name { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("$service_name") + .field("silent", &self.silent) + .finish() + } +} + +}; +} /// Get IPv4 addresses of all external network interfaces. fn get_interface_addresses() -> impl Iterator { @@ -299,17 +309,6 @@ fn get_interface_addresses() -> impl Iterator { }) } -impl fmt::Debug for $service_name { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("$service_name") - .field("silent", &self.silent) - .finish() - } -} - -}; -} - #[cfg(feature = "async-std")] codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, std::io::Error>(async_std::net::UdpSocket::from(socket)))); From 881ec698a8477544d173bcb6273daa7f27f6b181 Mon Sep 17 00:00:00 2001 From: Robert Klotzner Date: Tue, 22 Sep 2020 15:07:52 +0200 Subject: [PATCH 3/6] Untested multi interface implementation of mdns for async_std. tokio does not work yet on Windows, due to missing implementation of as_raw_handle. --- protocols/mdns/src/service.rs | 150 +++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 47 deletions(-) diff --git a/protocols/mdns/src/service.rs b/protocols/mdns/src/service.rs index 99b4b5e8d31..10a6dfa9656 100644 --- a/protocols/mdns/src/service.rs +++ b/protocols/mdns/src/service.rs @@ -111,12 +111,30 @@ macro_rules! codegen { /// # } #[cfg_attr(docsrs, doc(cfg(feature = $feature_name)))] pub struct $service_name { - /// Main socket for listening. + /// Sockets for sending messages. + /// + /// We have one socket per interface. Sending via the `receiving` socket would only send on the + /// default interfaces, which is picked by the operating system. This is usually what one + /// wants, but not always. The better approach to just picking some arbitrary interface is to + /// pick all interfaces, we do this by using a "sending" socket for each interfaces, which gets + /// bound to the interface IP address. Those sockets are therefore unsuited for receiving + /// multicast messages and will only be used for sending. + /// + /// Considerations: Having sockets only used for sending is obviously not ideal, as there are + /// input queues managed by the kernel which occupy memory. Nevertheless this approach is the + /// easiest to implement in a platform independent way. + /// + /// Other approaches evaluated: + /// + /// Using something like the [multicast-socket + /// crate](https://crates.io/crates/multicast-socket), which works by means of platform + /// specific functions like [sendmsg](https://linux.die.net/man/2/sendmsg) + /// + /// TODO: Correct above comment. + /// socket: $udp_socket, - - /// Socket for sending queries on the network. - query_socket: $udp_socket, - + /// IP addresses of the interfaces this implementation is sending packets out. + interfaces: Vec, /// Interval for sending queries. query_interval: Interval, /// Whether we send queries on the network at all. @@ -159,22 +177,18 @@ impl $service_name { }; let socket = $udp_socket_from_std(std_socket)?; - // Given that we pass an IP address to bind, which does not need to be resolved, we can - // use std::net::UdpSocket::bind, instead of its async counterpart from async-std. - let query_socket = $udp_socket_from_std( - std::net::UdpSocket::bind((Ipv4Addr::from([0u8, 0, 0, 0]), 0u16))?, - )?; socket.set_multicast_loop_v4(true)?; socket.set_multicast_ttl_v4(255)?; + let interfaces = get_interface_addresses().collect(); // Join multicast on all avaliable interfaces: - for addr in get_interface_addresses() { + for &addr in &interfaces { socket.join_multicast_v4(From::from([224, 0, 0, 251]), addr)?; } Ok($service_name { socket, - query_socket, + interfaces, query_interval: Interval::new_at(Instant::now(), Duration::from_secs(20)), silent, recv_buffer: [0; 2048], @@ -211,36 +225,39 @@ impl $service_name { // resolves, not forcing self-referential structures on the caller. pub async fn next(mut self) -> (Self, MdnsPacket) { loop { - // Flush the send buffer of the main socket. - while !self.send_buffers.is_empty() { - let to_send = self.send_buffers.remove(0); - - match self.socket.send_to(&to_send, *IPV4_MDNS_MULTICAST_ADDRESS).await { - Ok(bytes_written) => { - debug_assert_eq!(bytes_written, to_send.len()); - } - Err(_) => { - // Errors are non-fatal because they can happen for example if we lose - // connection to the network. - self.send_buffers.clear(); - break; + let send_buffers = self.send_buffers; + self.send_buffers = Vec::new(); + let query_send_buffers = self.query_send_buffers; + self.query_send_buffers = Vec::new(); + + for interface in &self.interfaces { + set_multicast_if_v4(&self.socket, interface) + .expect("set_multicast_if_v4 should work"); + // Flush the send buffer of the main socket. + for to_send in &send_buffers { + match self.socket.send_to(&to_send, *IPV4_MDNS_MULTICAST_ADDRESS).await { + Ok(bytes_written) => { + debug_assert_eq!(bytes_written, to_send.len()); + } + Err(_) => { + // Errors are non-fatal because they can happen for example if we lose + // connection to the network. + break; + } } } - } - - // Flush the query send buffer. - while !self.query_send_buffers.is_empty() { - let to_send = self.query_send_buffers.remove(0); - match self.query_socket.send_to(&to_send, *IPV4_MDNS_MULTICAST_ADDRESS).await { - Ok(bytes_written) => { - debug_assert_eq!(bytes_written, to_send.len()); - } - Err(_) => { - // Errors are non-fatal because they can happen for example if we lose - // connection to the network. - self.query_send_buffers.clear(); - break; + // Flush the query send buffer. + for to_send in &query_send_buffers { + match self.socket.send_to(&to_send, *IPV4_MDNS_MULTICAST_ADDRESS).await { + Ok(bytes_written) => { + debug_assert_eq!(bytes_written, to_send.len()); + } + Err(_) => { + // Errors are non-fatal because they can happen for example if we lose + // connection to the network. + break; + } } } } @@ -268,7 +285,7 @@ impl $service_name { // The query interval will wake up the task at some point so that we can try again. }, }, - Right(_) => { + Right(()) => { // Ensure underlying task is woken up on the next interval tick. while let Some(_) = self.query_interval.next().now_or_never() {}; @@ -293,6 +310,52 @@ impl fmt::Debug for $service_name { }; } +// Hack to make set_multicast_if_v4 available on asynchronous sockets: +#[cfg(feature = "async-std")] +fn set_multicast_if_v4(socket: &async_std::net::UdpSocket, interface: &Ipv4Addr) -> std::io::Result<()> { + use std::net::UdpSocket; + use net2::UdpSocketExt; + use async_std::os::unix::io::AsRawFd; + use std::os::unix::io::IntoRawFd; + use std::os::unix::io::FromRawFd; + // Temporary unsafe double ownership: + #[cfg(windows)] + let std_sock: UdpSocket = unsafe { + FromRawSocket::from_raw_socket(socket.as_raw_socket()) + }; + #[cfg(not(windows))] + let std_sock: UdpSocket = unsafe { + FromRawFd::from_raw_fd(socket.as_raw_fd()) + }; + // Don't use ? here, we need to drop the ownership in the end! + let r = std_sock.set_multicast_if_v4(interface); + // Drop ownership again, thus avoid double free! + std_sock.into_raw_fd(); + r +} +#[cfg(feature = "async-std")] +codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, std::io::Error>(async_std::net::UdpSocket::from(socket)))); + +// Hack to make set_multicast_if_v4 available on asynchronous sockets: +#[cfg(feature = "tokio")] +fn set_multicast_if_v4(socket: tokio::net::UdpSocket, interface: &Ipv4Addr) -> std::io::Result<()> { + use std::net::UdpSocket; + use net2::UdpSocketExt; + // Temporary unsafe double ownership: + #[cfg(windows)] + let std_sock = UdpSocket::from_raw_socket(socket.as_raw_socket()); + #[cfg(!windows)] + let std_sock = UdpSocket::from_raw_fd(socket.as_raw_fd()); + // Don't use ? here, we need to drop the ownership in the end! + let r = std_sock.set_multicast_if_v4(interface); + // Drop ownership again! + std_sock.to_raw_fd(); + r +} +#[cfg(feature = "tokio")] +codegen!("tokio", TokioMdnsService, tokio::net::UdpSocket, (|socket| tokio::net::UdpSocket::from_std(socket))); + + /// Get IPv4 addresses of all external network interfaces. fn get_interface_addresses() -> impl Iterator { datalink::interfaces() @@ -309,13 +372,6 @@ fn get_interface_addresses() -> impl Iterator { }) } -#[cfg(feature = "async-std")] -codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, std::io::Error>(async_std::net::UdpSocket::from(socket)))); - -#[cfg(feature = "tokio")] -codegen!("tokio", TokioMdnsService, tokio::net::UdpSocket, (|socket| tokio::net::UdpSocket::from_std(socket))); - - /// A valid mDNS packet received by the service. #[derive(Debug)] pub enum MdnsPacket { From 60218e4f8a078b6903ce3572e287413ba744ba73 Mon Sep 17 00:00:00 2001 From: Robert Klotzner Date: Thu, 24 Sep 2020 12:33:39 +0200 Subject: [PATCH 4/6] mdns: Avoid name clash tokio/async and fix tokio Have tokio variant and async-std variant of `set_multicast_if_v4` be named differntly and pass the name in into the code generation macro for the mdns service. --- protocols/mdns/src/service.rs | 43 +++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/protocols/mdns/src/service.rs b/protocols/mdns/src/service.rs index 10a6dfa9656..d8de71de527 100644 --- a/protocols/mdns/src/service.rs +++ b/protocols/mdns/src/service.rs @@ -39,7 +39,7 @@ lazy_static! { } macro_rules! codegen { - ($feature_name:expr, $service_name:ident, $udp_socket:ty, $udp_socket_from_std:tt) => { + ($feature_name:expr, $service_name:ident, $udp_socket:ty, $udp_socket_from_std:tt, $set_multicast_if_v4:tt) => { /// A running service that discovers libp2p peers and responds to other libp2p peers' queries on /// the local network. @@ -231,7 +231,7 @@ impl $service_name { self.query_send_buffers = Vec::new(); for interface in &self.interfaces { - set_multicast_if_v4(&self.socket, interface) + $set_multicast_if_v4(&self.socket, interface) .expect("set_multicast_if_v4 should work"); // Flush the send buffer of the main socket. for to_send in &send_buffers { @@ -310,13 +310,22 @@ impl fmt::Debug for $service_name { }; } -// Hack to make set_multicast_if_v4 available on asynchronous sockets: +#[cfg(feature = "async-std")] +codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, std::io::Error>(async_std::net::UdpSocket::from(socket))), set_multicast_if_v4); + +#[cfg(feature = "tokio")] +codegen!("tokio", TokioMdnsService, tokio::net::UdpSocket, (|socket| tokio::net::UdpSocket::from_std(socket)), set_multicast_if_v4_tokio); + +// Make set_multicast_if_v4 available on asynchronous sockets: #[cfg(feature = "async-std")] fn set_multicast_if_v4(socket: &async_std::net::UdpSocket, interface: &Ipv4Addr) -> std::io::Result<()> { use std::net::UdpSocket; use net2::UdpSocketExt; + #[cfg(not(windows))] use async_std::os::unix::io::AsRawFd; + #[cfg(not(windows))] use std::os::unix::io::IntoRawFd; + #[cfg(not(windows))] use std::os::unix::io::FromRawFd; // Temporary unsafe double ownership: #[cfg(windows)] @@ -333,27 +342,33 @@ fn set_multicast_if_v4(socket: &async_std::net::UdpSocket, interface: &Ipv4Addr) std_sock.into_raw_fd(); r } -#[cfg(feature = "async-std")] -codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, std::io::Error>(async_std::net::UdpSocket::from(socket)))); -// Hack to make set_multicast_if_v4 available on asynchronous sockets: +// Make set_multicast_if_v4 available on asynchronous sockets: #[cfg(feature = "tokio")] -fn set_multicast_if_v4(socket: tokio::net::UdpSocket, interface: &Ipv4Addr) -> std::io::Result<()> { +fn set_multicast_if_v4_tokio(socket: &tokio::net::UdpSocket, interface: &Ipv4Addr) -> std::io::Result<()> { use std::net::UdpSocket; use net2::UdpSocketExt; + #[cfg(not(windows))] + use std::os::unix::io::AsRawFd; + #[cfg(not(windows))] + use std::os::unix::io::IntoRawFd; + #[cfg(not(windows))] + use std::os::unix::io::FromRawFd; // Temporary unsafe double ownership: #[cfg(windows)] - let std_sock = UdpSocket::from_raw_socket(socket.as_raw_socket()); - #[cfg(!windows)] - let std_sock = UdpSocket::from_raw_fd(socket.as_raw_fd()); + let std_sock: UdpSocket = unsafe { + FromRawSocket::from_raw_socket(socket.as_raw_socket()) + }; + #[cfg(not(windows))] + let std_sock: UdpSocket = unsafe { + FromRawFd::from_raw_fd(socket.as_raw_fd()) + }; // Don't use ? here, we need to drop the ownership in the end! let r = std_sock.set_multicast_if_v4(interface); - // Drop ownership again! - std_sock.to_raw_fd(); + // Drop ownership again, thus avoid double free! + std_sock.into_raw_fd(); r } -#[cfg(feature = "tokio")] -codegen!("tokio", TokioMdnsService, tokio::net::UdpSocket, (|socket| tokio::net::UdpSocket::from_std(socket))); /// Get IPv4 addresses of all external network interfaces. From a136fca9560bc54d1137ddf3146c93ff694e28aa Mon Sep 17 00:00:00 2001 From: Robert Klotzner Date: Thu, 24 Sep 2020 12:59:25 +0200 Subject: [PATCH 5/6] Replace pnet and get-if-addrs with if-addrs. pnet is apparently rather hard to build on windows and get-if-addrs is no longer maintained. Replace both with now maintained if-addrs. --- protocols/mdns/Cargo.toml | 4 ++-- protocols/mdns/src/service.rs | 26 +++++++++++--------------- transports/tcp/Cargo.toml | 2 +- transports/tcp/src/lib.rs | 2 +- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 6b94f7b7c3f..72a040740a4 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -25,7 +25,7 @@ smallvec = "1.0" tokio = { version = "0.2", default-features = false, features = ["udp"], optional = true } void = "1.0" wasm-timer = "0.2.4" -pnet = "0.26.0" +if-addrs = "0.6.3" [dev-dependencies] -get_if_addrs = "0.5.3" +if-addrs = "0.6.3" diff --git a/protocols/mdns/src/service.rs b/protocols/mdns/src/service.rs index d8de71de527..f2319122611 100644 --- a/protocols/mdns/src/service.rs +++ b/protocols/mdns/src/service.rs @@ -23,9 +23,7 @@ use dns_parser::{Packet, RData}; use either::Either::{Left, Right}; use futures::{future, prelude::*}; use libp2p_core::{multiaddr::{Multiaddr, Protocol}, PeerId}; -use pnet::datalink; -use pnet::ipnetwork::IpNetwork; -use std::{convert::TryFrom as _, fmt, io, net::Ipv4Addr, net::SocketAddr, str, time::{Duration, Instant}}; +use std::{convert::TryFrom as _, fmt, io, net::IpAddr, net::Ipv4Addr, net::SocketAddr, str, time::{Duration, Instant}}; use wasm_timer::Interval; use lazy_static::lazy_static; @@ -180,7 +178,7 @@ impl $service_name { socket.set_multicast_loop_v4(true)?; socket.set_multicast_ttl_v4(255)?; - let interfaces = get_interface_addresses().collect(); + let interfaces = get_interface_addresses()?.collect(); // Join multicast on all avaliable interfaces: for &addr in &interfaces { socket.join_multicast_v4(From::from([224, 0, 0, 251]), addr)?; @@ -372,19 +370,17 @@ fn set_multicast_if_v4_tokio(socket: &tokio::net::UdpSocket, interface: &Ipv4Add /// Get IPv4 addresses of all external network interfaces. -fn get_interface_addresses() -> impl Iterator { - datalink::interfaces() +fn get_interface_addresses() -> io::Result> { + Ok(if_addrs::get_if_addrs()? .into_iter() - .filter(|i| i.is_up() && !i.is_loopback()) + .filter(|i| !i.is_loopback()) .filter_map(|i| { - i.ips - .into_iter() - .filter_map(|n| match n { - IpNetwork::V4(n4) => Some(n4.ip()), - _ => None, - }) - .next() // Simply get the first valid IPv4. + match i.ip() { + IpAddr::V4(addr) => Some(addr), + _ => None, + } }) + ) } /// A valid mDNS packet received by the service. @@ -716,7 +712,7 @@ mod tests { // properties. #[test] fn respect_query_interval() { - let own_ips: Vec = get_if_addrs::get_if_addrs().unwrap() + let own_ips: Vec = if_addrs::get_if_addrs().unwrap() .into_iter() .map(|i| i.addr.ip()) .collect(); diff --git a/transports/tcp/Cargo.toml b/transports/tcp/Cargo.toml index 608810fbb0f..59c2e43df3f 100644 --- a/transports/tcp/Cargo.toml +++ b/transports/tcp/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] async-std = { version = "1.6.2", optional = true } futures = "0.3.1" futures-timer = "3.0" -get_if_addrs = "0.5.3" +if-addrs = "0.6.3" ipnet = "2.0.0" libp2p-core = { version = "0.22.0", path = "../../core" } log = "0.4.1" diff --git a/transports/tcp/src/lib.rs b/transports/tcp/src/lib.rs index ee6bceb7221..7336f59e15c 100644 --- a/transports/tcp/src/lib.rs +++ b/transports/tcp/src/lib.rs @@ -31,7 +31,7 @@ use futures::{future::{self, Ready}, prelude::*}; use futures_timer::Delay; -use get_if_addrs::{IfAddr, get_if_addrs}; +use if_addrs::{IfAddr, get_if_addrs}; use ipnet::{IpNet, Ipv4Net, Ipv6Net}; use libp2p_core::{ Transport, From d704c2764302b8636a623b585a3f08d5f4c16cad Mon Sep 17 00:00:00 2001 From: Robert Klotzner Date: Thu, 1 Oct 2020 10:51:53 +0200 Subject: [PATCH 6/6] mdns: Finish unique interface discovery Also factored out networking code in its own module and started a test suite. --- protocols/mdns/src/lib.rs | 3 + protocols/mdns/src/network.rs | 315 ++++++++++++++++++++++++++++++++++ protocols/mdns/src/service.rs | 76 +------- 3 files changed, 324 insertions(+), 70 deletions(-) create mode 100644 protocols/mdns/src/network.rs diff --git a/protocols/mdns/src/lib.rs b/protocols/mdns/src/lib.rs index 292bd01b13f..e6fd1aedf1d 100644 --- a/protocols/mdns/src/lib.rs +++ b/protocols/mdns/src/lib.rs @@ -46,3 +46,6 @@ mod behaviour; mod dns; pub mod service; + +/// Network related helper functions. +mod network; diff --git a/protocols/mdns/src/network.rs b/protocols/mdns/src/network.rs new file mode 100644 index 00000000000..10f9aab43c5 --- /dev/null +++ b/protocols/mdns/src/network.rs @@ -0,0 +1,315 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use if_addrs::IfAddr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::{collections::HashSet, io}; + +// Network related helper for the mdns implementation. + +// Make set_multicast_if_v4 available on asynchronous sockets: +#[cfg(feature = "async-std")] +/// Set [IP_MULTICAST_IF](https://www.man7.org/linux/man-pages/man7/ip.7.html) for an async socket. +pub fn set_multicast_if_v4( + socket: &async_std::net::UdpSocket, + interface: &Ipv4Addr, +) -> std::io::Result<()> { + #[cfg(not(windows))] + use async_std::os::unix::io::AsRawFd; + use net2::UdpSocketExt; + use std::net::UdpSocket; + #[cfg(not(windows))] + use std::os::unix::io::FromRawFd; + #[cfg(not(windows))] + use std::os::unix::io::IntoRawFd; + // Temporary unsafe double ownership: + #[cfg(windows)] + let std_sock: UdpSocket = unsafe { FromRawSocket::from_raw_socket(socket.as_raw_socket()) }; + #[cfg(not(windows))] + let std_sock: UdpSocket = unsafe { FromRawFd::from_raw_fd(socket.as_raw_fd()) }; + // The unsafe sections above are safe under the assumption that the following two lines of + // code won't panic. + // Don't use ? here, we need to drop the ownership in the end! + let r = std_sock.set_multicast_if_v4(interface); + // Drop ownership again, thus avoid double free! + std_sock.into_raw_fd(); + r +} + +// Make set_multicast_if_v4 available on asynchronous sockets: +#[cfg(feature = "tokio")] +pub fn set_multicast_if_v4_tokio( + socket: &tokio::net::UdpSocket, + interface: &Ipv4Addr, +) -> std::io::Result<()> { + use net2::UdpSocketExt; + use std::net::UdpSocket; + #[cfg(not(windows))] + use std::os::unix::io::AsRawFd; + #[cfg(not(windows))] + use std::os::unix::io::FromRawFd; + #[cfg(not(windows))] + use std::os::unix::io::IntoRawFd; + // Temporary unsafe double ownership: + #[cfg(windows)] + let std_sock: UdpSocket = unsafe { FromRawSocket::from_raw_socket(socket.as_raw_socket()) }; + #[cfg(not(windows))] + let std_sock: UdpSocket = unsafe { FromRawFd::from_raw_fd(socket.as_raw_fd()) }; + // The unsafe sections above are safe under the assumption that the following two lines of + // code won't panic. + // Don't use ? here, we need to drop the ownership in the end! + let r = std_sock.set_multicast_if_v4(interface); + // Drop ownership again, thus avoid double free! + std_sock.into_raw_fd(); + r +} + +/// Get IPv4 addresses of all external network interfaces. +/// +/// To avoid double broad casting with all its issues (see +/// [rfc6762](https://tools.ietf.org/html/rfc6762#page-42)), we only pick one interface for a any +/// given non link-local network, see `get_unique_interfaces`. This should ensure good operation in +/// almost all configurations. The only exception is multiple link local addresses bridged to the +/// same network, which needs to be tackled in the way described in the RFC. +pub fn get_interface_addresses() -> io::Result> { + // Ok(if_addrs::get_if_addrs()? + Ok(get_unique_interfaces()? + .into_iter() + .filter(|i| !i.is_loopback()) + .filter_map(|i| match i.ip() { + IpAddr::V4(addr) => Some(addr), + _ => None, + })) +} +/// Get all unique interfaces. +/// +/// This means all interfaces which do not belong to the same subnet as some other interface. For each subnet only one interface will be returned (except for link local addresses). +/// +/// Note: If one interface belongs to a smaller subnetwork which is part of the network of another +/// interface, the implementation might or might not return both, depending on whether the masking +/// with their corresponding netmask results in the same base network or not. +/// +/// Having a single machine with one interface being in a network which is a subnetwork of the +/// network of another interface is a weird enough configuration to make this inconsistent +/// behaviour justifyable and keeps the implementation simple. +/// +/// For more details on the exact behaviour see the unit test `test_get_interface_network_v4`. +fn get_unique_interfaces() -> io::Result> { + let mut seen_networks = HashSet::new(); + let mut result = Vec::new(); + for i in if_addrs::get_if_addrs()? { + let network = get_interface_network(&i); + if ip_is_link_local(&network) || !seen_networks.contains(&network) { + seen_networks.insert(network); + result.push(i); + } + } + Ok(result.into_iter()) +} + +/// Get the network of an interface +/// +/// by computing the logical bitwise conjunction between the address and the subnetmask. +fn get_interface_network(interface: &if_addrs::Interface) -> IpAddr { + // Very redundant code, but operations on arrays are rather limited right now, + // see: https://internals.rust-lang.org/t/collecting-iterators-into-arrays/10330 + // and https://github.com/rust-lang/rust/issues/44580 + match &interface.addr { + IfAddr::V4(addr) => { + let mut result: [u8; 4] = [0; 4]; + let ip_octets = addr.ip.octets(); + let net_octets = addr.netmask.octets(); + let elements: _ = ip_octets + .iter() + .zip(net_octets.iter()) + .zip(result.iter_mut()); + for ((addr, mask), result) in elements { + *result = addr & mask; + } + From::from(result) + } + IfAddr::V6(addr) => { + let mut result: [u8; 16] = [0; 16]; + let ip_octets = addr.ip.octets(); + let net_octets = addr.netmask.octets(); + let elements = ip_octets + .iter() + .zip(net_octets.iter()) + .zip(result.iter_mut()); + for ((addr, mask), result) in elements { + *result = addr & mask; + } + From::from(result) + } + } +} + +/// Helper for checking link local IPv6 and IPv4 addresses +fn ip_is_link_local(addr: &IpAddr) -> bool { + match addr { + IpAddr::V4(addr) => addr.is_link_local(), + IpAddr::V6(addr) => ip_is_unicast_link_local_strict(&addr), + } +} + +// Copied over +// [ip_is_unicast_link_local_strict](https://doc.rust-lang.org/nightly/std/net/struct.Ipv6Addr.html#method.is_unicast_link_local_strict) from the standard library (it has not yet been +// stabilized). +pub fn ip_is_unicast_link_local_strict(addr: &Ipv6Addr) -> bool { + (addr.segments()[0] & 0xffff) == 0xfe80 + && (addr.segments()[1] & 0xffff) == 0 + && (addr.segments()[2] & 0xffff) == 0 + && (addr.segments()[3] & 0xffff) == 0 +} + +#[cfg(test)] +mod test { + use super::*; + use if_addrs::{IfAddr, Ifv4Addr, Ifv6Addr, Interface}; + #[test] + fn test_get_interface_network_v4() { + let network1_a = Interface { + name: "gibberish".to_string(), + addr: IfAddr::V4(Ifv4Addr { + ip: Ipv4Addr::new(192, 168, 129, 10), + netmask: Ipv4Addr::new(255, 255, 128, 0), + broadcast: None, + }), + }; + // Other interface in the same network + let network1_b = Interface { + name: "gibberish2".to_string(), + addr: IfAddr::V4(Ifv4Addr { + ip: Ipv4Addr::new(192, 168, 130, 22), + netmask: Ipv4Addr::new(255, 255, 128, 0), + broadcast: None, + }), + }; + // Other interface in a different network + let network2_a = Interface { + name: "gibberish3".to_string(), + addr: IfAddr::V4(Ifv4Addr { + ip: Ipv4Addr::new(192, 168, 127, 22), + netmask: Ipv4Addr::new(255, 255, 128, 0), + broadcast: None, + }), + }; + // Same ip, but smaller netmask. + let network2_big_a = Interface { + name: "gibberish4".to_string(), + addr: IfAddr::V4(Ifv4Addr { + ip: Ipv4Addr::new(192, 168, 127, 22), + netmask: Ipv4Addr::new(255, 255, 0, 0), + broadcast: None, + }), + }; + assert_eq!( + get_interface_network(&network1_a), + IpAddr::V4(Ipv4Addr::new(192, 168, 128, 0)) + ); + assert_eq!( + get_interface_network(&network1_a), + get_interface_network(&network1_b) + ); + assert_ne!( + get_interface_network(&network1_a), + get_interface_network(&network2_a) + ); + // Different netmask, but masking returns the same IP: + assert_eq!( + get_interface_network(&network2_a), + get_interface_network(&network2_big_a) + ); + // Different netmask and masking returns different IP: + assert_ne!( + get_interface_network(&network1_a), + get_interface_network(&network2_big_a) + ); + assert_ne!( + get_interface_network(&network1_b), + get_interface_network(&network2_big_a) + ); + } + + #[test] + fn test_get_interface_network_v6() { + let network1_a = Interface { + name: "gibberish".to_string(), + addr: IfAddr::V6(Ifv6Addr { + ip: Ipv6Addr::new(10, 0, 0, 0, 0, 0, 0, 1), + netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0), + broadcast: None, + }), + }; + // Other interface in the same network + let network1_b = Interface { + name: "gibberish2".to_string(), + addr: IfAddr::V6(Ifv6Addr { + ip: Ipv6Addr::new(10, 0, 0, 0, 0, 0, 0, 2), + netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0), + broadcast: None, + }), + }; + // Other interface in a different network + let network2_a = Interface { + name: "gibberish3".to_string(), + addr: IfAddr::V6(Ifv6Addr { + ip: Ipv6Addr::new(10, 0, 0, 0, 10, 0, 0, 2), + netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0), + broadcast: None, + }), + }; + // Same ip, but smaller netmask. + let network2_big_a = Interface { + name: "gibberish4".to_string(), + addr: IfAddr::V6(Ifv6Addr { + ip: Ipv6Addr::new(10, 0, 0, 0, 10, 0, 0, 2), + netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0), + broadcast: None, + }), + }; + assert_eq!( + get_interface_network(&network1_a), + IpAddr::V6(Ipv6Addr::new(10, 0, 0, 0, 0, 0, 0, 0)) + ); + assert_eq!( + get_interface_network(&network1_a), + get_interface_network(&network1_b) + ); + assert_ne!( + get_interface_network(&network1_a), + get_interface_network(&network2_a) + ); + // Different netmask and masking returns different IP: + assert_ne!( + get_interface_network(&network2_a), + get_interface_network(&network2_big_a) + ); + // Different netmask, but masking returns the same IP: + assert_eq!( + get_interface_network(&network1_a), + get_interface_network(&network2_big_a) + ); + assert_eq!( + get_interface_network(&network1_b), + get_interface_network(&network2_big_a) + ); + } +} diff --git a/protocols/mdns/src/service.rs b/protocols/mdns/src/service.rs index f2319122611..29666cabb40 100644 --- a/protocols/mdns/src/service.rs +++ b/protocols/mdns/src/service.rs @@ -18,12 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{SERVICE_NAME, META_QUERY_SERVICE, dns}; +use crate::{SERVICE_NAME, META_QUERY_SERVICE, dns, network::get_interface_addresses}; +#[cfg(feature = "async-std")] +use crate::network::set_multicast_if_v4; +#[cfg(feature = "tokio")] +use crate::network::set_multicast_if_v4_tokio; use dns_parser::{Packet, RData}; use either::Either::{Left, Right}; use futures::{future, prelude::*}; use libp2p_core::{multiaddr::{Multiaddr, Protocol}, PeerId}; -use std::{convert::TryFrom as _, fmt, io, net::IpAddr, net::Ipv4Addr, net::SocketAddr, str, time::{Duration, Instant}}; +use std::{convert::TryFrom as _, fmt, io, net::Ipv4Addr, net::SocketAddr, str, time::{Duration, Instant}}; use wasm_timer::Interval; use lazy_static::lazy_static; @@ -314,74 +318,6 @@ codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, #[cfg(feature = "tokio")] codegen!("tokio", TokioMdnsService, tokio::net::UdpSocket, (|socket| tokio::net::UdpSocket::from_std(socket)), set_multicast_if_v4_tokio); -// Make set_multicast_if_v4 available on asynchronous sockets: -#[cfg(feature = "async-std")] -fn set_multicast_if_v4(socket: &async_std::net::UdpSocket, interface: &Ipv4Addr) -> std::io::Result<()> { - use std::net::UdpSocket; - use net2::UdpSocketExt; - #[cfg(not(windows))] - use async_std::os::unix::io::AsRawFd; - #[cfg(not(windows))] - use std::os::unix::io::IntoRawFd; - #[cfg(not(windows))] - use std::os::unix::io::FromRawFd; - // Temporary unsafe double ownership: - #[cfg(windows)] - let std_sock: UdpSocket = unsafe { - FromRawSocket::from_raw_socket(socket.as_raw_socket()) - }; - #[cfg(not(windows))] - let std_sock: UdpSocket = unsafe { - FromRawFd::from_raw_fd(socket.as_raw_fd()) - }; - // Don't use ? here, we need to drop the ownership in the end! - let r = std_sock.set_multicast_if_v4(interface); - // Drop ownership again, thus avoid double free! - std_sock.into_raw_fd(); - r -} - -// Make set_multicast_if_v4 available on asynchronous sockets: -#[cfg(feature = "tokio")] -fn set_multicast_if_v4_tokio(socket: &tokio::net::UdpSocket, interface: &Ipv4Addr) -> std::io::Result<()> { - use std::net::UdpSocket; - use net2::UdpSocketExt; - #[cfg(not(windows))] - use std::os::unix::io::AsRawFd; - #[cfg(not(windows))] - use std::os::unix::io::IntoRawFd; - #[cfg(not(windows))] - use std::os::unix::io::FromRawFd; - // Temporary unsafe double ownership: - #[cfg(windows)] - let std_sock: UdpSocket = unsafe { - FromRawSocket::from_raw_socket(socket.as_raw_socket()) - }; - #[cfg(not(windows))] - let std_sock: UdpSocket = unsafe { - FromRawFd::from_raw_fd(socket.as_raw_fd()) - }; - // Don't use ? here, we need to drop the ownership in the end! - let r = std_sock.set_multicast_if_v4(interface); - // Drop ownership again, thus avoid double free! - std_sock.into_raw_fd(); - r -} - - -/// Get IPv4 addresses of all external network interfaces. -fn get_interface_addresses() -> io::Result> { - Ok(if_addrs::get_if_addrs()? - .into_iter() - .filter(|i| !i.is_loopback()) - .filter_map(|i| { - match i.ip() { - IpAddr::V4(addr) => Some(addr), - _ => None, - } - }) - ) -} /// A valid mDNS packet received by the service. #[derive(Debug)]