From e3d2420c5acec20b1ce45ba747f41464e70bd3ec Mon Sep 17 00:00:00 2001 From: Josef Zoller Date: Mon, 23 Sep 2024 15:06:54 +0200 Subject: [PATCH] Add support for ip6zone in quic transport --- Cargo.lock | 15 ++-- Cargo.toml | 2 +- transports/quic/Cargo.toml | 1 + transports/quic/src/transport.rs | 142 ++++++++++++++++++++++++++----- 4 files changed, 130 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3d1cd0d76d..5dacc41d32f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3183,6 +3183,7 @@ dependencies = [ "futures", "futures-timer", "if-watch", + "libc", "libp2p-core", "libp2p-identity", "libp2p-muxer-test-harness", @@ -3841,9 +3842,9 @@ dependencies = [ [[package]] name = "multiaddr" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" dependencies = [ "arrayref", "byteorder", @@ -3854,7 +3855,7 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", "url", ] @@ -5471,18 +5472,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index c9fe928096d..5ad25248539 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,7 +116,7 @@ libp2p-websocket = { version = "0.44.0", path = "transports/websocket" } libp2p-websocket-websys = { version = "0.4.0", path = "transports/websocket-websys" } libp2p-webtransport-websys = { version = "0.4.0", path = "transports/webtransport-websys" } libp2p-yamux = { version = "0.46.0", path = "muxers/yamux" } -multiaddr = "0.18.1" +multiaddr = "0.18.2" multihash = "0.19.1" multistream-select = { version = "0.13.0", path = "misc/multistream-select" } prometheus-client = "0.22.2" diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 42cc8e54edb..b9b5092a62b 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -14,6 +14,7 @@ bytes = "1.6.0" futures = { workspace = true } futures-timer = "3.0.3" if-watch = "3.2.0" +libc = "0.2.155" libp2p-core = { workspace = true } libp2p-tls = { workspace = true } libp2p-identity = { workspace = true } diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 057d0f978d7..36ccfe90832 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -43,7 +43,7 @@ use socket2::{Domain, Socket, Type}; use std::collections::hash_map::{DefaultHasher, Entry}; use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, UdpSocket}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV6, UdpSocket}; use std::time::Duration; use std::{fmt, io}; use std::{ @@ -700,9 +700,42 @@ fn multiaddr_to_socketaddr( support_draft_29: bool, ) -> Option<(SocketAddr, ProtocolVersion, Option)> { let mut iter = addr.iter(); - let proto1 = iter.next()?; - let proto2 = iter.next()?; - let proto3 = iter.next()?; + + let socket_addr = match iter.next()? { + Protocol::Ip4(ip) => { + let Protocol::Udp(port) = iter.next()? else { + return None; + }; + + SocketAddr::new(ip.into(), port) + } + Protocol::Ip6(ip) => { + let mut next = iter.next()?; + + let if_index = if let Protocol::Ip6zone(zone) = next { + next = iter.next()?; + + zone.parse() + .ok() + .or_else(|| if_nametoindex(zone.as_ref()).ok())? + } else { + 0 + }; + + let Protocol::Udp(port) = next else { + return None; + }; + + SocketAddrV6::new(ip, port, 0, if_index).into() + } + _ => return None, + }; + + let version = match iter.next()? { + Protocol::QuicV1 => ProtocolVersion::V1, + Protocol::Quic if support_draft_29 => ProtocolVersion::Draft29, + _ => return None, + }; let mut peer_id = None; for proto in iter { @@ -713,21 +746,8 @@ fn multiaddr_to_socketaddr( _ => return None, } } - let version = match proto3 { - Protocol::QuicV1 => ProtocolVersion::V1, - Protocol::Quic if support_draft_29 => ProtocolVersion::Draft29, - _ => return None, - }; - match (proto1, proto2) { - (Protocol::Ip4(ip), Protocol::Udp(port)) => { - Some((SocketAddr::new(ip.into(), port), version, peer_id)) - } - (Protocol::Ip6(ip), Protocol::Udp(port)) => { - Some((SocketAddr::new(ip.into(), port), version, peer_id)) - } - _ => None, - } + Some((socket_addr, version, peer_id)) } /// Turns an IP address and port into the corresponding QUIC multiaddr. @@ -736,10 +756,62 @@ fn socketaddr_to_multiaddr(socket_addr: &SocketAddr, version: ProtocolVersion) - ProtocolVersion::V1 => Protocol::QuicV1, ProtocolVersion::Draft29 => Protocol::Quic, }; - Multiaddr::empty() - .with(socket_addr.ip().into()) - .with(Protocol::Udp(socket_addr.port())) - .with(quic_proto) + match socket_addr { + SocketAddr::V6(socket_addr) if socket_addr.scope_id() != 0 => { + let scope_id = socket_addr.scope_id(); + + let zone = if_indextoname(scope_id).unwrap_or_else(|_| scope_id.to_string()); + + Multiaddr::empty() + .with(Protocol::Ip6(*socket_addr.ip())) + .with(Protocol::Ip6zone(zone.into())) + .with(Protocol::Udp(socket_addr.port())) + .with(quic_proto) + } + _ => Multiaddr::empty() + .with(socket_addr.ip().into()) + .with(Protocol::Udp(socket_addr.port())) + .with(quic_proto), + } +} + +/// Returns the index of the network interface corresponding to the given name. +#[cfg(unix)] +fn if_nametoindex(name: impl Into>) -> io::Result { + let if_name = std::ffi::CString::new(name)?; + match unsafe { libc::if_nametoindex(if_name.as_ptr()) } { + 0 => Err(io::Error::last_os_error()), + if_index => Ok(if_index), + } +} + +/// Returns the name of the network interface corresponding to the given interface index. +#[cfg(unix)] +fn if_indextoname(name: u32) -> io::Result { + let mut buffer = [0; libc::IF_NAMESIZE]; + let maybe_name = unsafe { libc::if_indextoname(name, buffer.as_mut_ptr()) }; + if maybe_name.is_null() { + Err(io::Error::last_os_error()) + } else { + let cstr = unsafe { std::ffi::CStr::from_ptr(maybe_name) }; + Ok(cstr.to_string_lossy().into_owned()) + } +} + +#[cfg(not(unix))] +fn if_nametoindex(name: impl Into>) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "if_nametoindex is not supported on this platform", + )) +} + +#[cfg(not(unix))] +fn if_indextoname(name: u32) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, + "if_indextoname is not supported on this platform", + )) } #[cfg(test)] @@ -804,6 +876,32 @@ mod tests { None )) ); + assert_eq!( + multiaddr_to_socketaddr( + &"/ip6/::1/ip6zone/lo/udp/12345/quic-v1" + .parse::() + .unwrap(), + false + ), + Some(( + SocketAddrV6::new( + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), + 12345, + 0, + if_nametoindex("lo").unwrap() + ) + .into(), + ProtocolVersion::V1, + None + )) + ); + assert!(multiaddr_to_socketaddr( + &"/ip6/::1/ip6zone/doesnotexist/udp/12345/quic-v1" + .parse::() + .unwrap(), + false + ) + .is_none()); assert_eq!( multiaddr_to_socketaddr( &"/ip6/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/udp/8080/quic-v1"