Skip to content

Commit

Permalink
Add support for RecvOrigDstAddr on Linux
Browse files Browse the repository at this point in the history
Fixes #1767
  • Loading branch information
brianmay committed Jul 24, 2022
1 parent 7cc33c1 commit bb5b474
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/sys/socket/mod.rs
Expand Up @@ -757,6 +757,14 @@ pub enum ControlMessageOwned {
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Ipv4RecvDstAddr(libc::in_addr),
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Ipv4RecvOrigDstAddr(libc::sockaddr_in),
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Ipv6RecvOrigDstAddr(libc::sockaddr_in6),

/// UDP Generic Receive Offload (GRO) allows receiving multiple UDP
/// packets from a single sender.
Expand Down Expand Up @@ -916,6 +924,12 @@ impl ControlMessageOwned {
let dl = ptr::read_unaligned(p as *const libc::in_addr);
ControlMessageOwned::Ipv4RecvDstAddr(dl)
},
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
(libc::IPPROTO_IP, libc::IP_RECVORIGDSTADDR) => {
let dl = ptr::read_unaligned(p as *const libc::sockaddr_in);
ControlMessageOwned::Ipv4RecvOrigDstAddr(dl)
},
#[cfg(target_os = "linux")]
#[cfg(feature = "net")]
(libc::SOL_UDP, libc::UDP_GRO) => {
Expand All @@ -939,6 +953,12 @@ impl ControlMessageOwned {
let (err, addr) = Self::recv_err_helper::<sockaddr_in6>(p, len);
ControlMessageOwned::Ipv6RecvErr(err, addr)
},
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
(libc::IPPROTO_IPV6, libc::IPV6_RECVORIGDSTADDR) => {
let dl = ptr::read_unaligned(p as *const libc::sockaddr_in6);
ControlMessageOwned::Ipv6RecvOrigDstAddr(dl)
},
(_, _) => {
let sl = slice::from_raw_parts(p, len);
let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(sl));
Expand Down
14 changes: 14 additions & 0 deletions src/sys/socket/sockopt.rs
Expand Up @@ -574,6 +574,13 @@ sockopt_impl!(
/// The `recvmsg(2)` call will return the destination IP address for a UDP
/// datagram.
Ipv4RecvDstAddr, Both, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, bool);
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
sockopt_impl!(
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
/// The `recvmsg(2)` call will return the destination IP address for a UDP
/// datagram.
Ipv4RecvOrigDstAddr, Both, libc::IPPROTO_IP, libc::IP_RECVORIGDSTADDR, bool);
#[cfg(target_os = "linux")]
#[cfg(feature = "net")]
sockopt_impl!(
Expand Down Expand Up @@ -621,6 +628,13 @@ sockopt_impl!(
sockopt_impl!(
/// Set the unicast hop limit for the socket.
Ipv6Ttl, Both, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS, libc::c_int);
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
sockopt_impl!(
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
/// The `recvmsg(2)` call will return the destination IP address for a UDP
/// datagram.
Ipv6RecvOrigDstAddr, Both, libc::IPPROTO_IPV6, libc::IPV6_RECVORIGDSTADDR, bool);
#[cfg(any(target_os = "ios", target_os = "macos"))]
sockopt_impl!(
/// Set "don't fragment packet" flag on the IP packet.
Expand Down
168 changes: 168 additions & 0 deletions test/sys/test_socket.rs
Expand Up @@ -1745,6 +1745,174 @@ pub fn test_recvif() {
}
}

#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[test]
pub fn test_recvif_ipv4() {
use nix::sys::socket::sockopt::{Ipv4RecvOrigDstAddr};
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, &lo).expect("bind failed");
let sa: SockaddrIn = getsockname(receive).expect("getsockname failed");
setsockopt(receive, Ipv4RecvOrigDstAddr, &true)
.expect("setsockopt IP_RECVORIGDSTADDR failed");

{
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");
sendmsg(send, &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!(libc::sockaddr_in);
let msg = recvmsg::<()>(
receive,
&mut iovec,
Some(&mut space),
MsgFlags::empty(),
)
.expect("recvmsg failed");
assert!(!msg
.flags
.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs");

let mut rx_recvorigdstaddr = false;
for cmsg in msg.cmsgs() {
match cmsg {
ControlMessageOwned::Ipv4RecvOrigDstAddr(addr) => {
rx_recvorigdstaddr = true;
if let Some(sin) = lo.as_sockaddr_in() {
assert_eq!(sin.as_ref().sin_addr.s_addr,
addr.sin_addr.s_addr,
"unexpected destination address (expected {}, got {})",
sin.as_ref().sin_addr.s_addr,
addr.sin_addr.s_addr);
} else {
panic!("unexpected Sockaddr");
}
}
_ => panic!("unexpected additional control msg"),
}
}
assert!(rx_recvorigdstaddr);
assert_eq!(msg.bytes, 8);
assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
}
}

#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[test]
pub fn test_recvif_ipv6() {
use nix::sys::socket::sockopt::{Ipv6RecvOrigDstAddr};
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, &lo).expect("bind failed");
let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed");
setsockopt(receive, Ipv6RecvOrigDstAddr, &true)
.expect("setsockopt IP_RECVORIGDSTADDR failed");

{
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");
sendmsg(send, &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!(libc::sockaddr_in6);
let msg = recvmsg::<()>(
receive,
&mut iovec,
Some(&mut space),
MsgFlags::empty(),
)
.expect("recvmsg failed");
assert!(!msg
.flags
.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs");

let mut rx_recvorigdstaddr = false;
for cmsg in msg.cmsgs() {
match cmsg {
ControlMessageOwned::Ipv6RecvOrigDstAddr(addr) => {
rx_recvorigdstaddr = true;
if let Some(sin) = lo.as_sockaddr_in6() {
assert_eq!(sin.as_ref().sin6_addr.s6_addr,
addr.sin6_addr.s6_addr,
"unexpected destination address (expected {:?}, got {:?})",
sin.as_ref().sin6_addr.s6_addr,
addr.sin6_addr.s6_addr);
} else {
panic!("unexpected Sockaddr");
}
}
_ => panic!("unexpected additional control msg"),
}
}
assert!(rx_recvorigdstaddr);
assert_eq!(msg.bytes, 8);
assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
}
}

#[cfg(any(
target_os = "android",
target_os = "freebsd",
Expand Down

0 comments on commit bb5b474

Please sign in to comment.