diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 9ddee2842b6..72a040740a4 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" +if-addrs = "0.6.3" [dev-dependencies] -get_if_addrs = "0.5.3" +if-addrs = "0.6.3" 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 b3913812c54..29666cabb40 100644 --- a/protocols/mdns/src/service.rs +++ b/protocols/mdns/src/service.rs @@ -18,7 +18,11 @@ // 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::*}; @@ -37,7 +41,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. @@ -109,12 +113,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. @@ -157,20 +179,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)?; - // TODO: correct interfaces? - socket.join_multicast_v4(From::from([224, 0, 0, 251]), Ipv4Addr::UNSPECIFIED)?; + 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)?; + } Ok($service_name { socket, - query_socket, + interfaces, query_interval: Interval::new_at(Instant::now(), Duration::from_secs(20)), silent, recv_buffer: [0; 2048], @@ -207,36 +227,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; + } } } } @@ -264,7 +287,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() {}; @@ -290,10 +313,10 @@ impl fmt::Debug for $service_name { } #[cfg(feature = "async-std")] -codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, std::io::Error>(async_std::net::UdpSocket::from(socket)))); +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))); +codegen!("tokio", TokioMdnsService, tokio::net::UdpSocket, (|socket| tokio::net::UdpSocket::from_std(socket)), set_multicast_if_v4_tokio); /// A valid mDNS packet received by the service. @@ -625,7 +648,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,