summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/sys/socket/mod.rs128
-rw-r--r--src/sys/socket/sockopt.rs16
-rw-r--r--test/sys/test_socket.rs235
4 files changed, 374 insertions, 7 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e8135b5..c29e7a16 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
+- Added PKTINFO(V4) & V6PKTINFO cmsg support - Android/FreeBSD/iOS/Linux/MacOS.
+ ([#990](https://github.com/nix-rust/nix/pull/990))
### Added
- Added support of CString type in `setsockopt`.
([#972](https://github.com/nix-rust/nix/pull/972))
diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs
index 2ac0e24a..c776cf5b 100644
--- a/src/sys/socket/mod.rs
+++ b/src/sys/socket/mod.rs
@@ -28,7 +28,7 @@ pub use self::addr::{
Ipv6Addr,
LinkAddr,
};
-#[cfg(any(target_os = "linux", target_os = "android"))]
+#[cfg(any(target_os = "android", target_os = "linux"))]
pub use ::sys::socket::addr::netlink::NetlinkAddr;
pub use libc::{
@@ -153,7 +153,7 @@ libc_bitflags!{
/// This flag specifies that queued errors should be received from
/// the socket error queue. (For more details, see
/// [recvfrom(2)](https://linux.die.net/man/2/recvfrom))
- #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[cfg(any(target_os = "android", target_os = "linux"))]
MSG_ERRQUEUE;
/// Set the `close-on-exec` flag for the file descriptor received via a UNIX domain
/// file descriptor using the `SCM_RIGHTS` operation (described in
@@ -526,6 +526,23 @@ pub enum ControlMessage<'a> {
/// nix::unistd::close(in_socket).unwrap();
/// ```
ScmTimestamp(&'a TimeVal),
+
+ #[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ Ipv4PacketInfo(&'a libc::in_pktinfo),
+ #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ Ipv6PacketInfo(&'a libc::in6_pktinfo),
+
/// Catch-all variant for unimplemented cmsg types.
#[doc(hidden)]
Unknown(UnknownCmsg<'a>),
@@ -565,12 +582,57 @@ impl<'a> ControlMessage<'a> {
ControlMessage::ScmTimestamp(t) => {
mem::size_of_val(t)
},
+ #[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv4PacketInfo(pktinfo) => {
+ mem::size_of_val(pktinfo)
+ },
+ #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv6PacketInfo(pktinfo) => {
+ mem::size_of_val(pktinfo)
+ },
ControlMessage::Unknown(UnknownCmsg(_, bytes)) => {
mem::size_of_val(bytes)
}
}
}
+ /// Returns the value to put into the `cmsg_level` field of the header.
+ fn cmsg_level(&self) -> libc::c_int {
+ match *self {
+ ControlMessage::ScmRights(_) => libc::SOL_SOCKET,
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ ControlMessage::ScmCredentials(_) => libc::SOL_SOCKET,
+ ControlMessage::ScmTimestamp(_) => libc::SOL_SOCKET,
+ #[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv4PacketInfo(_) => libc::IPPROTO_IP,
+ #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv6PacketInfo(_) => libc::IPPROTO_IPV6,
+ ControlMessage::Unknown(ref cmsg) => cmsg.0.cmsg_level,
+ }
+ }
+
/// Returns the value to put into the `cmsg_type` field of the header.
fn cmsg_type(&self) -> libc::c_int {
match *self {
@@ -578,6 +640,21 @@ impl<'a> ControlMessage<'a> {
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::ScmCredentials(_) => libc::SCM_CREDENTIALS,
ControlMessage::ScmTimestamp(_) => libc::SCM_TIMESTAMP,
+ #[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv4PacketInfo(_) => libc::IP_PKTINFO,
+ #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv6PacketInfo(_) => libc::IPV6_PKTINFO,
ControlMessage::Unknown(ref cmsg) => cmsg.0.cmsg_type,
}
}
@@ -598,7 +675,7 @@ impl<'a> ControlMessage<'a> {
} else {
let cmsg = cmsghdr {
cmsg_len: self.len() as _,
- cmsg_level: libc::SOL_SOCKET,
+ cmsg_level: self.cmsg_level(),
cmsg_type: self.cmsg_type(),
..mem::zeroed() // zero out platform-dependent padding fields
};
@@ -615,10 +692,29 @@ impl<'a> ControlMessage<'a> {
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::ScmCredentials(creds) => {
copy_bytes(creds, buf)
- }
+ },
ControlMessage::ScmTimestamp(t) => {
copy_bytes(t, buf)
},
+ #[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv4PacketInfo(pktinfo) => {
+ copy_bytes(pktinfo, buf)
+ },
+ #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ ControlMessage::Ipv6PacketInfo(pktinfo) => {
+ copy_bytes(pktinfo, buf)
+ }
ControlMessage::Unknown(_) => unreachable!(),
}
};
@@ -650,6 +746,28 @@ impl<'a> ControlMessage<'a> {
ControlMessage::ScmTimestamp(
&*(data.as_ptr() as *const _))
},
+ #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => {
+ ControlMessage::Ipv6PacketInfo(
+ &*(data.as_ptr() as *const _))
+ }
+ #[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+ ))]
+ (libc::IPPROTO_IP, libc::IP_PKTINFO) => {
+ ControlMessage::Ipv4PacketInfo(
+ &*(data.as_ptr() as *const _))
+ }
+
(_, _) => {
ControlMessage::Unknown(UnknownCmsg(header, data))
}
@@ -1055,7 +1173,7 @@ pub unsafe fn sockaddr_storage_to_addr(
let pathlen = len - offset_of!(sockaddr_un, sun_path);
Ok(SockAddr::Unix(UnixAddr(sun, pathlen)))
}
- #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[cfg(any(target_os = "android", target_os = "linux"))]
libc::AF_NETLINK => {
use libc::sockaddr_nl;
Ok(SockAddr::Netlink(NetlinkAddr(*(addr as *const _ as *const sockaddr_nl))))
diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs
index f166d997..3cd46c66 100644
--- a/src/sys/socket/sockopt.rs
+++ b/src/sys/socket/sockopt.rs
@@ -271,6 +271,22 @@ sockopt_impl!(Both, Mark, libc::SOL_SOCKET, libc::SO_MARK, u32);
sockopt_impl!(Both, PassCred, libc::SOL_SOCKET, libc::SO_PASSCRED, bool);
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
sockopt_impl!(Both, TcpCongestion, libc::IPPROTO_TCP, libc::TCP_CONGESTION, OsString<[u8; TCP_CA_NAME_MAX]>);
+#[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+))]
+sockopt_impl!(Both, Ipv4PacketInfo, libc::IPPROTO_IP, libc::IP_PKTINFO, bool);
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+))]
+sockopt_impl!(Both, Ipv6RecvPacketInfo, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, bool);
+
/*
*
diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs
index 3329417d..5730fb86 100644
--- a/test/sys/test_socket.rs
+++ b/test/sys/test_socket.rs
@@ -201,10 +201,10 @@ fn test_scm_rights_single_cmsg_multiple_fds() {
}
assert!(cmsgs.next().is_none(), "unexpected control msg");
- assert_eq!(iovec[0].as_slice(), [1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8]);
+ assert_eq!(iovec[0].as_slice(), [1u8, 2, 3, 4, 5, 6, 7, 8]);
});
- let slice = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8];
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
let iov = [IoVec::from_slice(&slice)];
let fds = [libc::STDIN_FILENO, libc::STDOUT_FILENO]; // pass stdin and stdout
let cmsg = [ControlMessage::ScmRights(&fds)];
@@ -451,3 +451,234 @@ pub fn test_syscontrol() {
// requires root privileges
// connect(fd, &sockaddr).expect("connect failed");
}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+))]
+// qemu doesn't seem to be emulating this correctly in these architectures
+#[cfg_attr(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc64",
+), ignore)]
+#[test]
+pub fn test_recv_ipv4pktinfo() {
+ use libc;
+ use nix::ifaddrs::{getifaddrs, InterfaceAddress};
+ use nix::net::if_::*;
+ use nix::sys::socket::sockopt::Ipv4PacketInfo;
+ use nix::sys::socket::{bind, AddressFamily, SockFlag, SockType};
+ use nix::sys::socket::{getsockname, setsockopt, socket, SockAddr};
+ use nix::sys::socket::{recvmsg, sendmsg, CmsgSpace, ControlMessage, MsgFlags};
+ use nix::sys::uio::IoVec;
+ use std::io;
+ use std::io::Write;
+ use std::thread;
+
+ fn loopback_v4addr() -> Option<InterfaceAddress> {
+ let addrs = match getifaddrs() {
+ Ok(iter) => iter,
+ Err(e) => {
+ let stdioerr = io::stderr();
+ let mut handle = stdioerr.lock();
+ writeln!(handle, "getifaddrs: {:?}", e).unwrap();
+ return None;
+ },
+ };
+ for ifaddr in addrs {
+ if ifaddr.flags.contains(InterfaceFlags::IFF_LOOPBACK) {
+ match ifaddr.address {
+ Some(SockAddr::Inet(InetAddr::V4(..))) => {
+ return Some(ifaddr);
+ }
+ _ => continue,
+ }
+ }
+ }
+ None
+ }
+
+ let lo_ifaddr = loopback_v4addr();
+ 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 = getsockname(receive).expect("getsockname failed");
+ setsockopt(receive, Ipv4PacketInfo, &true).expect("setsockopt failed");
+
+ let thread = thread::spawn(move || {
+ let mut buf = [0u8; 8];
+ let iovec = [IoVec::from_mut_slice(&mut buf)];
+ let mut space = CmsgSpace::<libc::in_pktinfo>::new();
+ let msg = recvmsg(
+ receive,
+ &iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ ).expect("recvmsg failed");
+ assert!(
+ !msg.flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)
+ );
+
+ let mut cmsgs = msg.cmsgs();
+ match cmsgs.next() {
+ Some(ControlMessage::Ipv4PacketInfo(pktinfo)) => {
+ let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex");
+ assert_eq!(
+ pktinfo.ipi_ifindex as libc::c_uint,
+ i,
+ "unexpected ifindex (expected {}, got {})",
+ i,
+ pktinfo.ipi_ifindex
+ );
+ }
+ _ => (),
+ }
+ assert!(cmsgs.next().is_none(), "unexpected additional control msg");
+ assert_eq!(
+ iovec[0].as_slice(),
+ [1u8, 2, 3, 4, 5, 6, 7, 8]
+ );
+ });
+
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoVec::from_slice(&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");
+
+ thread.join().unwrap();
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+))]
+// qemu doesn't seem to be emulating this correctly in these architectures
+#[cfg_attr(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc64",
+), ignore)]
+#[test]
+pub fn test_recv_ipv6pktinfo() {
+ use libc;
+ use nix::ifaddrs::{getifaddrs, InterfaceAddress};
+ use nix::net::if_::*;
+ use nix::sys::socket::sockopt::Ipv6RecvPacketInfo;
+ use nix::sys::socket::{bind, AddressFamily, SockFlag, SockType};
+ use nix::sys::socket::{getsockname, setsockopt, socket, SockAddr};
+ use nix::sys::socket::{recvmsg, sendmsg, CmsgSpace, ControlMessage, MsgFlags};
+ use nix::sys::uio::IoVec;
+ use std::io;
+ use std::io::Write;
+ use std::thread;
+
+ fn loopback_v6addr() -> Option<InterfaceAddress> {
+ let addrs = match getifaddrs() {
+ Ok(iter) => iter,
+ Err(e) => {
+ let stdioerr = io::stderr();
+ let mut handle = stdioerr.lock();
+ writeln!(handle, "getifaddrs: {:?}", e).unwrap();
+ return None;
+ },
+ };
+ for ifaddr in addrs {
+ if ifaddr.flags.contains(InterfaceFlags::IFF_LOOPBACK) {
+ match ifaddr.address {
+ Some(SockAddr::Inet(InetAddr::V6(..))) => {
+ return Some(ifaddr);
+ }
+ _ => continue,
+ }
+ }
+ }
+ None
+ }
+
+ let lo_ifaddr = loopback_v6addr();
+ 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::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ ).expect("receive socket failed");
+ bind(receive, &lo).expect("bind failed");
+ let sa = getsockname(receive).expect("getsockname failed");
+ setsockopt(receive, Ipv6RecvPacketInfo, &true).expect("setsockopt failed");
+
+ let thread = thread::spawn(move || {
+ let mut buf = [0u8; 8];
+ let iovec = [IoVec::from_mut_slice(&mut buf)];
+ let mut space = CmsgSpace::<libc::in6_pktinfo>::new();
+ let msg = recvmsg(
+ receive,
+ &iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ ).expect("recvmsg failed");
+ assert!(
+ !msg.flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)
+ );
+
+ let mut cmsgs = msg.cmsgs();
+ match cmsgs.next() {
+ Some(ControlMessage::Ipv6PacketInfo(pktinfo)) => {
+ let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex");
+ assert_eq!(
+ pktinfo.ipi6_ifindex,
+ i,
+ "unexpected ifindex (expected {}, got {})",
+ i,
+ pktinfo.ipi6_ifindex
+ );
+ }
+ _ => (),
+ }
+ assert!(cmsgs.next().is_none(), "unexpected additional control msg");
+ assert_eq!(
+ iovec[0].as_slice(),
+ [1u8, 2, 3, 4, 5, 6, 7, 8]
+ );
+ });
+
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoVec::from_slice(&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");
+
+ thread.join().unwrap();
+}