From 3f8c6cb6ebd5121da48abb405177ca441cff3a79 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 30 Jan 2024 16:36:54 -0800 Subject: [PATCH] feat(relay): allow channel bindings to IPv6 addresses (#3434) Previously, we still had a hard-coded rule in the relay that would not allow us to relay to an IPv6 peer. We can remove that and properly check this based on the allocated addresses. Resolves: #3405. --- rust/relay/src/server.rs | 13 +++++-- rust/relay/tests/regression.rs | 66 +++++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/rust/relay/src/server.rs b/rust/relay/src/server.rs index 55daebcd5..6e813a478 100644 --- a/rust/relay/src/server.rs +++ b/rust/relay/src/server.rs @@ -1027,9 +1027,18 @@ impl Channel { } impl Allocation { + /// Checks whether this [`Allocation`] can relay to the given address. + /// + /// This is called in the context of a channel binding with the requested peer address. + /// We can only relay to the address if the allocation supports the same version of the IP protocol. fn can_relay_to(&self, addr: SocketAddr) -> bool { - // Currently, we only support IPv4, thus any IPv6 address is invalid. - addr.is_ipv4() + match addr { + SocketAddr::V4(_) => self.first_relay_addr.is_ipv4(), // If we have an IPv4 address, it is in `first_relay_addr`, no need to check `second_relay_addr`. + SocketAddr::V6(_) => { + self.first_relay_addr.is_ipv6() + || self.second_relay_addr.is_some_and(|a| a.is_ipv6()) + } + } } } diff --git a/rust/relay/tests/regression.rs b/rust/relay/tests/regression.rs index 239f4696f..4aa2d58e5 100644 --- a/rust/relay/tests/regression.rs +++ b/rust/relay/tests/regression.rs @@ -7,7 +7,7 @@ use rand::rngs::mock::StepRng; use secrecy::SecretString; use std::collections::HashMap; use std::iter; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::time::{Duration, SystemTime}; use stun_codec::rfc5389::attributes::{ErrorCode, Nonce, Realm, Username, XorMappedAddress}; use stun_codec::rfc5389::errors::Unauthorized; @@ -377,28 +377,36 @@ fn ping_pong_relay( } #[proptest] -fn can_make_ipv6_allocation( - #[strategy(firezone_relay::proptest::transaction_id())] transaction_id: TransactionId, +fn ping_pong_ip6_relay( + #[strategy(firezone_relay::proptest::transaction_id())] allocate_transaction_id: TransactionId, + #[strategy(firezone_relay::proptest::transaction_id())] + channel_bind_transaction_id: TransactionId, #[strategy(firezone_relay::proptest::allocation_lifetime())] lifetime: Lifetime, #[strategy(firezone_relay::proptest::username_salt())] username_salt: String, - source: SocketAddrV4, + #[strategy(firezone_relay::proptest::channel_number())] channel: ChannelNumber, + source: SocketAddrV6, + peer: SocketAddrV6, public_relay_ip4_addr: Ipv4Addr, public_relay_ip6_addr: Ipv6Addr, #[strategy(firezone_relay::proptest::now())] now: SystemTime, + peer_to_client_ping: [u8; 32], + client_to_peer_ping: [u8; 32], #[strategy(firezone_relay::proptest::nonce())] nonce: Uuid, ) { + let _ = env_logger::try_init(); + let mut server = TestServer::new((public_relay_ip4_addr, public_relay_ip6_addr)).with_nonce(nonce); - let secret = server.auth_secret(); + let secret = server.auth_secret().to_owned(); server.assert_commands( from_client( source, Allocate::new_authenticated_udp_ip6( - transaction_id, + allocate_transaction_id, Some(lifetime.clone()), valid_username(now, &username_salt), - secret, + &secret, nonce, ), now, @@ -409,7 +417,7 @@ fn can_make_ipv6_allocation( send_message( source, allocate_response( - transaction_id, + allocate_transaction_id, public_relay_ip6_addr, 49152, source, @@ -418,6 +426,46 @@ fn can_make_ipv6_allocation( ), ], ); + + let now = now + Duration::from_secs(1); + + server.assert_commands( + from_client( + source, + ChannelBind::new( + channel_bind_transaction_id, + channel, + XorPeerAddress::new(peer.into()), + valid_username(now, &username_salt), + &secret, + nonce, + ), + now, + ), + [send_message( + source, + channel_bind_response(channel_bind_transaction_id), + )], + ); + + let now = now + Duration::from_secs(1); + + server.assert_commands( + from_client( + source, + ChannelData::new(channel.value(), client_to_peer_ping.as_ref()), + now, + ), + [forward(peer, &client_to_peer_ping, 49152)], + ); + + server.assert_commands( + from_peer(peer, peer_to_client_ping.as_ref(), 49152), + [send_channel_data( + source, + ChannelData::new(channel.value(), peer_to_client_ping.as_ref()), + )], + ); } struct TestServer { @@ -590,7 +638,7 @@ fn allocate_response( transaction_id: TransactionId, public_relay_addr: impl Into, port: u16, - source: SocketAddrV4, + source: impl Into, lifetime: &Lifetime, ) -> Message { let mut message =