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 25, 2022
1 parent 7cc33c1 commit c45cd74
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -30,6 +30,10 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1752](https://github.com/nix-rust/nix/pull/1752))
- Added `signal::SigSet::from_sigset_t_unchecked()`.
(#[1741](https://github.com/nix-rust/nix/pull/1741))
- Added IP_ORIGDSTADDR using Ipv4OrigDstAddr in setsockopt and recvmsg.
(#[1772](https://github.com/nix-rust/nix/pull/1772))
- Added IPV6_ORIGDSTADDR using Ipv6OrigDstAddr in setsockopt and recvmsg.
(#[1772](https://github.com/nix-rust/nix/pull/1772))

### Changed

Expand Down
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")))]
Ipv4OrigDstAddr(libc::sockaddr_in),
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
Ipv6OrigDstAddr(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_ORIGDSTADDR) => {
let dl = ptr::read_unaligned(p as *const libc::sockaddr_in);
ControlMessageOwned::Ipv4OrigDstAddr(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_ORIGDSTADDR) => {
let dl = ptr::read_unaligned(p as *const libc::sockaddr_in6);
ControlMessageOwned::Ipv6OrigDstAddr(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.
Ipv4OrigDstAddr, Both, libc::IPPROTO_IP, libc::IP_ORIGDSTADDR, 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.
Ipv6OrigDstAddr, Both, libc::IPPROTO_IPV6, libc::IPV6_ORIGDSTADDR, bool);
#[cfg(any(target_os = "ios", target_os = "macos"))]
sockopt_impl!(
/// Set "don't fragment packet" flag on the IP packet.
Expand Down
170 changes: 170 additions & 0 deletions test/sys/test_socket.rs
Expand Up @@ -1745,6 +1745,176 @@ pub fn test_recvif() {
}
}

#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
#[cfg_attr(qemu, ignore)]
#[test]
pub fn test_recvif_ipv4() {
use nix::sys::socket::sockopt::Ipv4OrigDstAddr;
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, Ipv4OrigDstAddr, &true)
.expect("setsockopt IP_ORIGDSTADDR 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::Ipv4OrigDstAddr(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"))]
#[cfg_attr(qemu, ignore)]
#[test]
pub fn test_recvif_ipv6() {
use nix::sys::socket::sockopt::Ipv6OrigDstAddr;
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, Ipv6OrigDstAddr, &true)
.expect("setsockopt IP_ORIGDSTADDR 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::Ipv6OrigDstAddr(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 c45cd74

Please sign in to comment.