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
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
75 changes: 54 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]
#[cfg_attr(target_os = "windows", ignore)]
Ralith marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -156,33 +194,28 @@ fn test_send_recv(send: &Socket, recv: &Socket, transmit: Transmit) {

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);
}
(false, true) => {
assert_eq!(ip_to_v6_mapped(addr), Ipv4Addr::LOCALHOST.to_ipv6_mapped());
}
(true, true) => {
if addr != Ipv6Addr::LOCALHOST && addr != Ipv4Addr::LOCALHOST.to_ipv6_mapped() {
Ralith marked this conversation as resolved.
Show resolved Hide resolved
panic!()
}
}
}
}
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