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

Add support for RecvOrigDstAddr on Linux #1772

Merged
merged 1 commit into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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.
brianmay marked this conversation as resolved.
Show resolved Hide resolved
(#[1772](https://github.com/nix-rust/nix/pull/1772))

### Changed

Expand Down
20 changes: 20 additions & 0 deletions src/sys/socket/mod.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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