From 812a4706f6c5341089c7a4c40bc29a8aa2937058 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Mon, 4 Jan 2021 18:21:03 +0000 Subject: [PATCH] Allow to use the incoming IP address for sending outgoing packets This is a follow-up for #943. When a socket is bound to a wildcard IP address, sending the outgoing IP might use a different source IP address than the one the packet was received on, since the OS might not be able to identify the necessary route. This would lead packets not allowing to reach the client. This change adds a setting which will set an explicit source address in all outgoing packets. The source address which will be used is the local IP address which was used to receive the initial incoming packet. --- quinn-proto/src/connection/mod.rs | 2 ++ quinn-proto/src/endpoint.rs | 15 ++++++++++++--- quinn-proto/src/lib.rs | 10 +++++++++- quinn/src/platform/unix.rs | 30 ++++++++++++++++++++++++++++-- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index b0e4cabd2..970cce0df 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -365,6 +365,7 @@ where contents: buf, ecn: None, segment_size: None, + src_ip: self.local_ip, }); } } @@ -554,6 +555,7 @@ where None }, segment_size: None, + src_ip: self.local_ip, }) } diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index 151724818..72cb25a79 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -190,6 +190,7 @@ where ecn: None, contents: buf, segment_size: None, + src_ip: local_ip, }); return None; } @@ -253,7 +254,7 @@ where if !self.is_server() { debug!("packet for unrecognized connection {}", dst_cid); - self.stateless_reset(datagram_len, remote, &dst_cid); + self.stateless_reset(datagram_len, remote, local_ip, &dst_cid); return None; } @@ -288,7 +289,7 @@ where // if !dst_cid.is_empty() { - self.stateless_reset(datagram_len, remote, &dst_cid); + self.stateless_reset(datagram_len, remote, local_ip, &dst_cid); } else { trace!("dropping unrecognized short packet without ID"); } @@ -299,6 +300,7 @@ where &mut self, inciting_dgram_len: usize, remote: SocketAddr, + local_ip: Option, dst_cid: &ConnectionId, ) { /// Minimum amount of padding for the stateless reset to look like a short-header packet @@ -336,6 +338,7 @@ where ecn: None, contents: buf, segment_size: None, + src_ip: local_ip, }); } @@ -535,6 +538,7 @@ where debug!("refusing connection"); self.initial_close( remote, + local_ip, crypto, &src_cid, &temp_loc_cid, @@ -553,6 +557,7 @@ where ); self.initial_close( remote, + local_ip, crypto, &src_cid, &temp_loc_cid, @@ -590,6 +595,7 @@ where ecn: None, contents: buf, segment_size: None, + src_ip: local_ip, }); return None; } @@ -608,6 +614,7 @@ where debug!("rejecting invalid stateless retry token"); self.initial_close( remote, + local_ip, crypto, &src_cid, &temp_loc_cid, @@ -645,7 +652,7 @@ where debug!("handshake failed: {}", e); self.handle_event(ch, EndpointEvent(EndpointEventInner::Drained)); if let ConnectionError::TransportError(e) = e { - self.initial_close(remote, crypto, &src_cid, &temp_loc_cid, e); + self.initial_close(remote, local_ip, crypto, &src_cid, &temp_loc_cid, e); } None } @@ -655,6 +662,7 @@ where fn initial_close( &mut self, destination: SocketAddr, + local_ip: Option, crypto: &Keys, remote_id: &ConnectionId, local_id: &ConnectionId, @@ -683,6 +691,7 @@ where ecn: None, contents: buf, segment_size: None, + src_ip: local_ip, }) } diff --git a/quinn-proto/src/lib.rs b/quinn-proto/src/lib.rs index 6703e9441..23d210f72 100644 --- a/quinn-proto/src/lib.rs +++ b/quinn-proto/src/lib.rs @@ -18,7 +18,13 @@ #![allow(clippy::cognitive_complexity)] #![allow(clippy::too_many_arguments)] -use std::{convert::TryInto, fmt, net::SocketAddr, ops, time::Duration}; +use std::{ + convert::TryInto, + fmt, + net::{IpAddr, SocketAddr}, + ops, + time::Duration, +}; mod cid_queue; #[doc(hidden)] @@ -277,6 +283,8 @@ pub struct Transmit { /// The segment size if this transmission contains multiple datagrams. /// This is `None` if the transmit only contains a single datagram pub segment_size: Option, + /// Optional source IP address for the datagram + pub src_ip: Option, } // diff --git a/quinn/src/platform/unix.rs b/quinn/src/platform/unix.rs index f99ad4100..36b7a8788 100644 --- a/quinn/src/platform/unix.rs +++ b/quinn/src/platform/unix.rs @@ -2,7 +2,7 @@ use std::{ io, io::IoSliceMut, mem::{self, MaybeUninit}, - net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, os::unix::io::AsRawFd, ptr, }; @@ -273,7 +273,7 @@ pub fn caps() -> UdpCapabilities { *CAPABILITIES } -const CMSG_LEN: usize = 64; +const CMSG_LEN: usize = 80; fn prepare_msg( transmit: &Transmit, @@ -312,6 +312,32 @@ fn prepare_msg( gso::set_segment_size(&mut encoder, segment_size as u16); } + if let Some(ip) = &transmit.src_ip { + if cfg!(target_os = "linux") { + match ip { + IpAddr::V4(v4) => { + let pktinfo = libc::in_pktinfo { + ipi_ifindex: 0, + ipi_spec_dst: unsafe { + *(v4 as *const Ipv4Addr as *const () as *const libc::in_addr) + }, + ipi_addr: libc::in_addr { s_addr: 0 }, + }; + encoder.push(libc::IPPROTO_IP, libc::IP_PKTINFO, pktinfo); + } + IpAddr::V6(v6) => { + let pktinfo = libc::in6_pktinfo { + ipi6_ifindex: 0, + ipi6_addr: unsafe { + *(v6 as *const Ipv6Addr as *const () as *const libc::in6_addr) + }, + }; + encoder.push(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO, pktinfo); + } + } + } + } + encoder.finish(); }