Skip to content

Commit

Permalink
fix(quinn-udp): use TOS for IPv4-mapped IPv6 dst addrs
Browse files Browse the repository at this point in the history
  • Loading branch information
mxinden committed Feb 22, 2024
1 parent 614fef6 commit a947962
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 23 deletions.
5 changes: 4 additions & 1 deletion quinn-udp/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,10 @@ fn prepare_msg(
hdr.msg_controllen = CMSG_LEN as _;
let mut encoder = unsafe { cmsg::Encoder::new(hdr) };
let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int);
if transmit.destination.is_ipv4() {
// True for IPv4 or IPv4-Mapped IPv6
let is_ipv4 = transmit.destination.is_ipv4()
|| matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some());
if is_ipv4 {
if !sendmsg_einval {
encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
}
Expand Down
5 changes: 4 additions & 1 deletion quinn-udp/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ impl UdpSocketState {

// ECN is a C integer https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ecn
let ecn = transmit.ecn.map_or(0, |x| x as c_int);
if transmit.destination.is_ipv4() {
// True for IPv4 or IPv4-Mapped IPv6
let is_ipv4 = transmit.destination.is_ipv4()
|| matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some());
if is_ipv4 {
encoder.push(WinSock::IPPROTO_IP, WinSock::IP_ECN, ecn);
} else {
encoder.push(WinSock::IPPROTO_IPV6, WinSock::IPV6_ECN, ecn);
Expand Down
77 changes: 56 additions & 21 deletions quinn-udp/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,44 @@ fn ecn_v4() {
}
}

#[test]
fn ecn_v4_mapped_v6() {
let send = socket2::Socket::new(
socket2::Domain::IPV6,
socket2::Type::DGRAM,
Some(socket2::Protocol::UDP),
)
.unwrap();
send.set_only_v6(false).unwrap();
send.bind(&socket2::SockAddr::from(
"[::]:0".parse::<SocketAddr>().unwrap(),
))
.unwrap();

let recv = UdpSocket::bind("127.0.0.1:0").unwrap();
let recv = Socket::from(recv);
let recv_v4_mapped_v6 = SocketAddr::V6(SocketAddrV6::new(
Ipv4Addr::LOCALHOST.to_ipv6_mapped(),
recv.local_addr().unwrap().as_socket().unwrap().port(),
0,
0,
));

for codepoint in [EcnCodepoint::Ect0, EcnCodepoint::Ect1] {
test_send_recv(
&send,
&recv,
Transmit {
destination: recv_v4_mapped_v6,
ecn: Some(codepoint),
contents: Bytes::from_static(b"hello"),
segment_size: None,
src_ip: None,
},
);
}
}

#[test]
#[cfg_attr(not(any(target_os = "linux", target_os = "windows")), ignore)]
fn gso() {
Expand Down Expand Up @@ -154,35 +192,32 @@ fn test_send_recv(send: &Socket, recv: &Socket, transmit: Transmit) {
}
datagrams += segments;

assert_eq!(
meta.addr.port(),
send.local_addr().unwrap().as_socket().unwrap().port()
);
let send_v6 = send.local_addr().unwrap().as_socket().unwrap().is_ipv6();
let recv_v6 = recv.local_addr().unwrap().as_socket().unwrap().is_ipv6();
match send_v6 == recv_v6 {
true => assert_eq!(meta.addr, send.local_addr().unwrap().as_socket().unwrap()),
false => assert_eq!(
meta.addr,
to_v6_mapped(send.local_addr().unwrap().as_socket().unwrap())
),
}
assert_eq!(meta.ecn, transmit.ecn);
let src = meta.addr.ip();
let dst = meta.dst_ip.unwrap();
match (send_v6, recv_v6) {
(_, false) => assert_eq!(dst, Ipv4Addr::LOCALHOST),
// Windows gives us real IPv4 addrs, whereas *nix use IPv6-mapped IPv4
// addrs. Canonicalize to IPv6-mapped for robustness.
(false, true) => assert_eq!(ip_to_v6_mapped(dst), Ipv4Addr::LOCALHOST.to_ipv6_mapped()),
(true, true) => assert_eq!(dst, Ipv6Addr::LOCALHOST),
for addr in [src, dst] {
match (send_v6, recv_v6) {
(_, false) => assert_eq!(addr, Ipv4Addr::LOCALHOST),
// Windows gives us real IPv4 addrs, whereas *nix use IPv6-mapped IPv4
// addrs. Canonicalize to IPv6-mapped for robustness.
(false, true) => {
assert_eq!(ip_to_v6_mapped(addr), Ipv4Addr::LOCALHOST.to_ipv6_mapped())
}
(true, true) => assert!(
addr == Ipv6Addr::LOCALHOST || addr == Ipv4Addr::LOCALHOST.to_ipv6_mapped()
),
}
}
assert_eq!(meta.ecn, transmit.ecn);
}
assert_eq!(datagrams, expected_datagrams);
}