From 78347d1618492ee9930a036fd835710b616ccd15 Mon Sep 17 00:00:00 2001 From: Perry Lorier Date: Sun, 26 Apr 2020 13:16:21 +0100 Subject: [PATCH] Add Ipv{4,6}PacketInfo support to ControlMessage for send{m,}msg This adds Ipv4PacketInfo and Ipv6PacketInfo to ControlMessage, allowing these to be used with sendmsg/sendmmsg. This change contains the following squashed commits: Add Ipv{4,6}PacketInfo to ControlMessage. Add documentation links to Ipv{4,6}PacketInfo Add changelog entry for Ipv{4,6}PacketInfo Add link to PR in the Changelog. Add extra build environments. Add tests for Ipv{4,6}PacketInfo. Swap #[test] and #[cfg] The CI appears to be running the test, even though it's not cfg'd for that platform. I _think_ this might be due to these being in the wrong order. So lets try swapping them. s/freebsd/netbsd/ for Ipv4PacketInfo netbsd supports in_pktinfo, not freebsd. Fix the cfg for Ipv{4,6}PacketInfo usage. Ah, I see what I did wrong. I had fixed the definitions, but I had the wrong cfg() in the usage. This has the usage match the definitions. Change SOL_IPV6 to IPPROTO_IPV6. FreeBSD doesn't have SOL_IPV6, but does have IPPROTO_IPV6, and the two constants are defined as being equal. So change to use IPPROTO_IPV6. Skip Ipv6PacketInfo test if v6 is not available. If IPv6 is not available, then when we try and bind to ip6-localhost, we'll get a EADDRNOTAVAIL, so skip the test. This should mean that the test will run on any machine that has a v6 loopback address. More architecture cfg() fixes. These all need to be the same, and they were not. Make them them all the same. Attempt III. Fix up mismatched cfg's again. Take IV. Make sure the cfg's that use a enum variant match the enum definition. --- CHANGELOG.md | 2 + src/sys/socket/mod.rs | 43 ++++++++++++++++ test/sys/test_socket.rs | 105 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 346af45bdc..481a1a2c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. + (#[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)) diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 6909c154c9..b972835f89 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -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), } // An opaque structure used to prevent cmsghdr from being a public type @@ -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( @@ -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), } } @@ -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, } } @@ -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, } } diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index d6c1ec0cbb..a5fa54bd57 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -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 +// 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