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 Ipv{4,6}PacketInfo support to ControlMessage for send{m,}msg. #1222

Merged
merged 1 commit into from Jun 27, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -33,6 +33,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
(#[1233](https://github.com/nix-rust/nix/pull/1233))
- Added `EventFilter` bitflags for `EV_DISPATCH` and `EV_RECEIPT` on OpenBSD.
(#[1252](https://github.com/nix-rust/nix/pull/1252))
- Added support for `Ipv4PacketInfo` and `Ipv6PacketInfo` to `ControlMessage`.
isomer marked this conversation as resolved.
Show resolved Hide resolved
(#[1222](https://github.com/nix-rust/nix/pull/1222))

### Changed
- Changed `fallocate` return type from `c_int` to `()` (#[1201](https://github.com/nix-rust/nix/pull/1201))
Expand Down
43 changes: 43 additions & 0 deletions src/sys/socket/mod.rs
Expand Up @@ -717,6 +717,25 @@ pub enum ControlMessage<'a> {
/// following one by one, and the last, possibly smaller one.
#[cfg(target_os = "linux")]
UdpGsoSegments(&'a u16),

/// Configure the sending addressing and interface for v4
///
/// For further information, please refer to the
/// [`ip(7)`](http://man7.org/linux/man-pages/man7/ip.7.html) man page.
#[cfg(any(target_os = "linux",
target_os = "macos",
target_os = "netbsd"))]
Ipv4PacketInfo(&'a libc::in_pktinfo),

/// Configure the sending addressing and interface for v6
///
/// For further information, please refer to the
/// [`ipv6(7)`](http://man7.org/linux/man-pages/man7/ipv6.7.html) man page.
#[cfg(any(target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "freebsd"))]
Ipv6PacketInfo(&'a libc::in6_pktinfo),
isomer marked this conversation as resolved.
Show resolved Hide resolved
}

// An opaque structure used to prevent cmsghdr from being a public type
Expand Down Expand Up @@ -798,6 +817,12 @@ impl<'a> ControlMessage<'a> {
ControlMessage::UdpGsoSegments(gso_size) => {
gso_size as *const _ as *const u8
},
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd"))]
ControlMessage::Ipv4PacketInfo(info) => info as *const _ as *const u8,
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd", target_os = "freebsd"))]
ControlMessage::Ipv6PacketInfo(info) => info as *const _ as *const u8,
};
unsafe {
ptr::copy_nonoverlapping(
Expand Down Expand Up @@ -838,6 +863,12 @@ impl<'a> ControlMessage<'a> {
ControlMessage::UdpGsoSegments(gso_size) => {
mem::size_of_val(gso_size)
},
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd"))]
ControlMessage::Ipv4PacketInfo(info) => mem::size_of_val(info),
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd", target_os = "freebsd"))]
ControlMessage::Ipv6PacketInfo(info) => mem::size_of_val(info),
}
}

Expand All @@ -854,6 +885,12 @@ impl<'a> ControlMessage<'a> {
ControlMessage::AlgSetAeadAssoclen(_) => libc::SOL_ALG,
#[cfg(target_os = "linux")]
ControlMessage::UdpGsoSegments(_) => libc::SOL_UDP,
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd"))]
ControlMessage::Ipv4PacketInfo(_) => libc::IPPROTO_IP,
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd", target_os = "freebsd"))]
ControlMessage::Ipv6PacketInfo(_) => libc::IPPROTO_IPV6,
}
}

Expand Down Expand Up @@ -881,6 +918,12 @@ impl<'a> ControlMessage<'a> {
ControlMessage::UdpGsoSegments(_) => {
libc::UDP_SEGMENT
},
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd"))]
ControlMessage::Ipv4PacketInfo(_) => libc::IP_PKTINFO,
#[cfg(any(target_os = "linux", target_os = "macos",
target_os = "netbsd", target_os = "freebsd"))]
ControlMessage::Ipv6PacketInfo(_) => libc::IPV6_PKTINFO,
}
}

Expand Down
105 changes: 105 additions & 0 deletions test/sys/test_socket.rs
Expand Up @@ -666,6 +666,111 @@ pub fn test_af_alg_aead() {
assert_eq!(decrypted[(assoc_size as usize)..(payload_len + (assoc_size as usize))], payload[(assoc_size as usize)..payload_len + (assoc_size as usize)]);
}

// Verify `ControlMessage::Ipv4PacketInfo` for `sendmsg`.
// This creates a (udp) socket bound to localhost, then sends a message to
// itself but uses Ipv4PacketInfo to force the source address to be localhost.
//
// This would be a more interesting test if we could assume that the test host
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the CI systems as well as most dev systems won't have multiple IP addresses. But if you really want to make a great demonstration of the feature, you could add a binary in the examples/ directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think a good demo here is particularly useful. These are low level APIs that people shouldn't be using directly. Somewhere there should be a crate that abstracts over all of this and provides something like set_local_address() and figures out what the correct underlying implementation to use (of which these are only one implementation). For example, for FreeBSD, Ipv6PacketInfo is the correct answer, but there's a different solution for IPv4.

I just thought I'd document why the test seems to be particularly inane. The test should be useful, if anything goes wrong, the kernel will reject it and the test will catch that.

// has more than one IP address (since we could select a different address to
// test from).
#[cfg(any(target_os = "linux",
target_os = "macos",
target_os = "netbsd"))]
#[test]
pub fn test_sendmsg_ipv4packetinfo() {
use nix::sys::uio::IoVec;
use nix::sys::socket::{socket, sendmsg, bind,
AddressFamily, SockType, SockFlag, SockAddr,
ControlMessage, MsgFlags};

let sock = socket(AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None)
.expect("socket failed");

let std_sa = SocketAddr::from_str("127.0.0.1:4000").unwrap();
let inet_addr = InetAddr::from_std(&std_sa);
let sock_addr = SockAddr::new_inet(inet_addr);

bind(sock, &sock_addr).expect("bind failed");

let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
let iov = [IoVec::from_slice(&slice)];

if let InetAddr::V4(sin) = inet_addr {
let pi = libc::in_pktinfo {
ipi_ifindex: 0, /* Unspecified interface */
ipi_addr: libc::in_addr { s_addr: 0 },
ipi_spec_dst: sin.sin_addr,
};

let cmsg = [ControlMessage::Ipv4PacketInfo(&pi)];

sendmsg(sock, &iov, &cmsg, MsgFlags::empty(), Some(&sock_addr))
.expect("sendmsg");
} else {
panic!("No IPv4 addresses available for testing?");
}
}

// Verify `ControlMessage::Ipv6PacketInfo` for `sendmsg`.
// This creates a (udp) socket bound to ip6-localhost, then sends a message to
// itself but uses Ipv6PacketInfo to force the source address to be
// ip6-localhost.
//
// This would be a more interesting test if we could assume that the test host
// has more than one IP address (since we could select a different address to
// test from).
#[cfg(any(target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "freebsd"))]
#[test]
pub fn test_sendmsg_ipv6packetinfo() {
use nix::Error;
use nix::errno::Errno;
use nix::sys::uio::IoVec;
use nix::sys::socket::{socket, sendmsg, bind,
AddressFamily, SockType, SockFlag, SockAddr,
ControlMessage, MsgFlags};

let sock = socket(AddressFamily::Inet6,
SockType::Datagram,
SockFlag::empty(),
None)
.expect("socket failed");

let std_sa = SocketAddr::from_str("[::1]:6000").unwrap();
let inet_addr = InetAddr::from_std(&std_sa);
let sock_addr = SockAddr::new_inet(inet_addr);

match bind(sock, &sock_addr) {
Err(Error::Sys(Errno::EADDRNOTAVAIL)) => {
println!("IPv6 not available, skipping test.");
return;
},
_ => (),
}

let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
let iov = [IoVec::from_slice(&slice)];

if let InetAddr::V6(sin) = inet_addr {
let pi = libc::in6_pktinfo {
ipi6_ifindex: 0, /* Unspecified interface */
ipi6_addr: sin.sin6_addr,
};

let cmsg = [ControlMessage::Ipv6PacketInfo(&pi)];

sendmsg(sock, &iov, &cmsg, MsgFlags::empty(), Some(&sock_addr))
.expect("sendmsg");
} else {
println!("No IPv6 addresses available for testing: skipping testing Ipv6PacketInfo");
}
}

/// Tests that passing multiple fds using a single `ControlMessage` works.
// Disable the test on emulated platforms due to a bug in QEMU versions <
// 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808
Expand Down