Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(quinn-udp): use TOS for IPv4-mapped IPv6 dst addrs #1765

Merged
merged 17 commits into from
Feb 22, 2024
Merged
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);
}

fn to_v6_mapped(x: SocketAddr) -> SocketAddr {
match x {
SocketAddr::V4(x) => SocketAddrV6::new(x.ip().to_ipv6_mapped(), x.port(), 0, 0).into(),
SocketAddr::V6(_) => x,
}
}

fn ip_to_v6_mapped(x: IpAddr) -> IpAddr {
match x {
IpAddr::V4(x) => IpAddr::V6(x.to_ipv6_mapped()),
Expand Down