Skip to content

Commit

Permalink
Add support for receive ToS/TClass IP socket options
Browse files Browse the repository at this point in the history
  • Loading branch information
spencercw committed Dec 4, 2023
1 parent da45140 commit e984d94
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/2081.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `IP_RECVTOS` and `IPV6_RECVTCLASS` socket options.
20 changes: 20 additions & 0 deletions src/sys/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,14 @@ pub enum ControlMessageOwned {
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Ipv6PacketInfo(libc::in6_pktinfo),
#[cfg(any(linux_android, target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Ipv4Tos(u8),
#[cfg(any(linux_android, netbsdlike, target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Ipv6TClass(u32),
#[cfg(bsd)]
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Expand Down Expand Up @@ -931,6 +939,18 @@ impl ControlMessageOwned {
let info = unsafe { ptr::read_unaligned(p as *const libc::in_pktinfo) };
ControlMessageOwned::Ipv4PacketInfo(info)
}
#[cfg(any(linux_android, target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[cfg(feature = "net")]
(libc::IPPROTO_IP, libc::IP_TOS) => {
let tos = unsafe { ptr::read_unaligned(p as *const u8) };
ControlMessageOwned::Ipv4Tos(tos)
}
#[cfg(any(linux_android, netbsdlike, target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[cfg(feature = "net")]
(libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => {
let tclass = unsafe { ptr::read_unaligned(p as *const u32) };
ControlMessageOwned::Ipv6TClass(tclass)
}
#[cfg(bsd)]
#[cfg(feature = "net")]
(libc::IPPROTO_IP, libc::IP_RECVIF) => {
Expand Down
24 changes: 24 additions & 0 deletions src/sys/socket/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,30 @@ sockopt_impl!(
libc::IPV6_RECVPKTINFO,
bool
);
#[cfg(any(linux_android, target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[cfg(feature = "net")]
sockopt_impl!(
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
/// Set delivery of the `IP_TOS` control message on incoming
/// datagrams.
Ipv4RecvTos,
Both,
libc::IPPROTO_IP,
libc::IP_RECVTOS,
bool
);
#[cfg(any(linux_android, netbsdlike, target_os = "freebsd", target_os = "ios", target_os = "macos"))]
#[cfg(feature = "net")]
sockopt_impl!(
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
/// Set delivery of the `IPV6_TCLASS` control message on incoming
/// datagrams.
Ipv6RecvTClass,
Both,
libc::IPPROTO_IPV6,
libc::IPV6_RECVTCLASS,
bool
);
#[cfg(bsd)]
#[cfg(feature = "net")]
sockopt_impl!(
Expand Down
156 changes: 156 additions & 0 deletions test/sys/test_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2257,6 +2257,162 @@ pub fn test_recv_ipv6pktinfo() {
}
}

#[cfg(target_os = "linux")]
#[test]
pub fn test_tos_ipv4() {
use nix::sys::socket::sockopt::{IpTos, Ipv4RecvTos};
use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn};
use nix::sys::socket::{getsockname, setsockopt, socket};
use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags};
use std::io::{IoSlice, IoSliceMut};

let lo_ifaddr = loopback_address(AddressFamily::Inet);
let (_lo_name, lo) = match lo_ifaddr {
Some(ifaddr) => (
ifaddr.interface_name,
ifaddr.address.expect("Expect IPv4 address on interface"),
),
None => return,
};
let receive = socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None,
)
.expect("receive socket failed");
bind(receive.as_raw_fd(), &lo).expect("bind failed");
let sa: SockaddrIn =
getsockname(receive.as_raw_fd()).expect("getsockname failed");
setsockopt(&receive, Ipv4RecvTos, &true).expect("setsockopt failed");

let send_tos = 42;
{
let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
let iov = [IoSlice::new(&slice)];

let send = socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None,
)
.expect("send socket failed");
setsockopt(&send, IpTos, &send_tos).expect("setsockopt failed");
sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa))
.expect("sendmsg failed");
}

{
let mut buf = [0u8; 8];
let mut iovec = [IoSliceMut::new(&mut buf)];

let mut space = cmsg_space!(u8);
let msg = recvmsg::<()>(
receive.as_raw_fd(),
&mut iovec,
Some(&mut space),
MsgFlags::empty(),
)
.expect("recvmsg failed");
assert!(!msg
.flags
.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));

let mut cmsgs = msg.cmsgs();
let recv_tos = match cmsgs.next().expect("no control msg received") {
ControlMessageOwned::Ipv4Tos(value) => value,
cmsg => panic!("received unexpected control msg {:?}", cmsg),
};
assert_eq!(
recv_tos as libc::c_int, send_tos,
"unexpected tos (expected {}, got {})",
send_tos, recv_tos
);
assert!(cmsgs.next().is_none(), "unexpected additional control msg");
assert_eq!(msg.bytes, 8);
assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
}
}

#[cfg(target_os = "linux")]
#[test]
pub fn test_tclass_ipv6() {
use nix::sys::socket::sockopt::{Ipv6RecvTClass, Ipv6TClass};
use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn6};
use nix::sys::socket::{getsockname, setsockopt, socket};
use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags};
use std::io::{IoSlice, IoSliceMut};

let lo_ifaddr = loopback_address(AddressFamily::Inet6);
let (_lo_name, lo) = match lo_ifaddr {
Some(ifaddr) => (
ifaddr.interface_name,
ifaddr.address.expect("Expect IPv6 address on interface"),
),
None => return,
};
let receive = socket(
AddressFamily::Inet6,
SockType::Datagram,
SockFlag::empty(),
None,
)
.expect("receive socket failed");
bind(receive.as_raw_fd(), &lo).expect("bind failed");
let sa: SockaddrIn6 =
getsockname(receive.as_raw_fd()).expect("getsockname failed");
setsockopt(&receive, Ipv6RecvTClass, &true).expect("setsockopt failed");

let send_tclass = 42;
{
let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
let iov = [IoSlice::new(&slice)];

let send = socket(
AddressFamily::Inet6,
SockType::Datagram,
SockFlag::empty(),
None,
)
.expect("send socket failed");
setsockopt(&send, Ipv6TClass, &send_tclass).expect("setsockopt failed");
sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa))
.expect("sendmsg failed");
}

{
let mut buf = [0u8; 8];
let mut iovec = [IoSliceMut::new(&mut buf)];

let mut space = cmsg_space!(u32);
let msg = recvmsg::<()>(
receive.as_raw_fd(),
&mut iovec,
Some(&mut space),
MsgFlags::empty(),
)
.expect("recvmsg failed");
assert!(!msg
.flags
.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));

let mut cmsgs = msg.cmsgs();
let recv_tclass = match cmsgs.next().expect("no control msg received") {
ControlMessageOwned::Ipv6TClass(value) => value,
cmsg => panic!("received unexpected control msg {:?}", cmsg),
};
assert_eq!(
recv_tclass as libc::c_int, send_tclass,
"unexpected tclass (expected {}, got {})",
send_tclass, recv_tclass
);
assert!(cmsgs.next().is_none(), "unexpected additional control msg");
assert_eq!(msg.bytes, 8);
assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
}
}

#[cfg(linux_android)]
#[test]
pub fn test_vsock() {
Expand Down

0 comments on commit e984d94

Please sign in to comment.