Skip to content

Commit

Permalink
Allow to use the incoming IP address for sending outgoing packets
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Matthias Einwag authored and djc committed Jan 5, 2021
1 parent a01a4fa commit 67cf59b
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 5 deletions.
23 changes: 23 additions & 0 deletions quinn-proto/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,18 @@ where
/// Improves behavior for clients that move between different internet connections or suffer NAT
/// rebinding. Enabled by default.
pub(crate) migration: bool,

/// Whether to use the initial local IP address for outgoing packets
///
/// If set to `true`, the local IP address which was target of the initial
/// QUIC packet will be used as the source IP address for all outgoing packets.
/// If set to `false`, the source IP address will be the address the socket is
/// bound to.
///
/// The setting only has an effect on platforms where
/// [`quinn_proto::Connection::local_ip()`] returns a local IP address.
/// (Currently only on Linux.)
pub(crate) send_from_initial_ip: bool,
}

impl<S> ServerConfig<S>
Expand All @@ -453,6 +465,7 @@ where
concurrent_connections: 100_000,

migration: true,
send_from_initial_ip: true,
}
}

Expand Down Expand Up @@ -492,6 +505,14 @@ where
self.migration = value;
self
}

/// Whether to use the initial local IP address for outgoing packets
///
/// For more information, see the documentation for [`ServerConfig::send_from_initial_ip`].
pub fn send_from_initial_ip(&mut self, value: bool) -> &mut Self {
self.send_from_initial_ip = value;
self
}
}

#[cfg(feature = "rustls")]
Expand Down Expand Up @@ -520,6 +541,7 @@ where
.field("retry_token_lifetime", &self.retry_token_lifetime)
.field("concurrent_connections", &self.concurrent_connections)
.field("migration", &self.migration)
.field("send_from_initial_ip", &self.send_from_initial_ip)
.finish()
}
}
Expand Down Expand Up @@ -552,6 +574,7 @@ where
retry_token_lifetime: self.retry_token_lifetime,
concurrent_connections: self.concurrent_connections,
migration: self.migration,
send_from_initial_ip: self.send_from_initial_ip,
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions quinn-proto/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ where
contents: buf,
ecn: None,
segment_size: None,
src_ip: self.src_ip(),
});
}
}
Expand Down Expand Up @@ -561,6 +562,7 @@ where
None
},
segment_size: None,
src_ip: self.src_ip(),
})
}

Expand Down Expand Up @@ -719,6 +721,16 @@ where
pad
}

/// Returns the source IP used in outgoing packets.
///
/// Returns `None` if no specific IP should be used
fn src_ip(&self) -> Option<IpAddr> {
match self.server_config.as_ref()?.send_from_initial_ip {
true => self.local_ip,
false => None,
}
}

/// Indicates whether we're a server that hasn't validated the peer's address and hasn't
/// received enough data from the peer to permit additional sending
fn anti_amplification_blocked(&self) -> bool {
Expand Down
25 changes: 22 additions & 3 deletions quinn-proto/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ where
ecn: None,
contents: buf,
segment_size: None,
src_ip: self.src_ip(local_ip),
});
return None;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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");
}
Expand All @@ -299,6 +300,7 @@ where
&mut self,
inciting_dgram_len: usize,
remote: SocketAddr,
local_ip: Option<IpAddr>,
dst_cid: &ConnectionId,
) {
/// Minimum amount of padding for the stateless reset to look like a short-header packet
Expand Down Expand Up @@ -336,6 +338,7 @@ where
ecn: None,
contents: buf,
segment_size: None,
src_ip: self.src_ip(local_ip),
});
}

Expand Down Expand Up @@ -391,6 +394,16 @@ where
ConnectionEvent(ConnectionEventInner::NewIdentifiers(ids, now))
}

/// Returns the source IP used in outgoing packets.
///
/// Returns `None` if no specific IP should be used
fn src_ip(&self, local_ip: Option<IpAddr>) -> Option<IpAddr> {
match self.server_config.as_ref()?.send_from_initial_ip {
true => local_ip,
false => None,
}
}

fn new_cid(&mut self) -> ConnectionId {
loop {
let cid = self.local_cid_generator.generate_cid();
Expand Down Expand Up @@ -535,6 +548,7 @@ where
debug!("refusing connection");
self.initial_close(
remote,
local_ip,
crypto,
&src_cid,
&temp_loc_cid,
Expand All @@ -553,6 +567,7 @@ where
);
self.initial_close(
remote,
local_ip,
crypto,
&src_cid,
&temp_loc_cid,
Expand Down Expand Up @@ -590,6 +605,7 @@ where
ecn: None,
contents: buf,
segment_size: None,
src_ip: self.src_ip(local_ip),
});
return None;
}
Expand All @@ -608,6 +624,7 @@ where
debug!("rejecting invalid stateless retry token");
self.initial_close(
remote,
local_ip,
crypto,
&src_cid,
&temp_loc_cid,
Expand Down Expand Up @@ -645,7 +662,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
}
Expand All @@ -655,6 +672,7 @@ where
fn initial_close(
&mut self,
destination: SocketAddr,
local_ip: Option<IpAddr>,
crypto: &Keys<S>,
remote_id: &ConnectionId,
local_id: &ConnectionId,
Expand Down Expand Up @@ -683,6 +701,7 @@ where
ecn: None,
contents: buf,
segment_size: None,
src_ip: self.src_ip(local_ip),
})
}

Expand Down
10 changes: 9 additions & 1 deletion quinn-proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<usize>,
/// Optional source IP address for the datagram
pub src_ip: Option<IpAddr>,
}

//
Expand Down
8 changes: 8 additions & 0 deletions quinn/src/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ where
self.config.use_stateless_retry(enabled);
self
}

/// Whether to use the initial local IP address for outgoing packets
///
/// For more information, see the documentation for [`ServerConfig::send_from_initial_ip`].
pub fn send_from_initial_ip(&mut self, value: bool) -> &mut Self {
self.config.send_from_initial_ip(value);
self
}
}

#[cfg(feature = "rustls")]
Expand Down
28 changes: 27 additions & 1 deletion quinn/src/platform/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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: libc::in_addr { s_addr: 0 },
ipi_addr: unsafe {
*(v4 as *const Ipv4Addr as *const () as *const libc::in_addr)
},
};
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();
}

Expand Down

0 comments on commit 67cf59b

Please sign in to comment.