Skip to content

Commit

Permalink
Merge branch 'master' into socket-mss
Browse files Browse the repository at this point in the history
  • Loading branch information
asomers authored Sep 8, 2021
2 parents 67b4872 + 53fea96 commit b06c26f
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 2 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1503](https://github.com/nix-rust/nix/pull/1503))
- Enabled `pwritev` and `preadv` for more operating systems.
(#[1511](https://github.com/nix-rust/nix/pull/1511))
- Added support for `TCP_MAXSEG` TCP Maximum Segment Size socket options
Added support for `TCP_MAXSEG` TCP Maximum Segment Size socket options
(#[1292](https://github.com/nix-rust/nix/pull/1292))
- Added `Ipv4RecvErr` and `Ipv6RecvErr` sockopts and associated control messages.
(#[1514](https://github.com/nix-rust/nix/pull/1514))
- Added `AsRawFd` implementation on `PollFd`.
(#[1516](https://github.com/nix-rust/nix/pull/1516))

### Changed

Expand Down
8 changes: 7 additions & 1 deletion src/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::sys::time::TimeSpec;
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))]
use crate::sys::signal::SigSet;
use std::os::unix::io::RawFd;
use std::os::unix::io::{AsRawFd, RawFd};

use crate::Result;
use crate::errno::Errno;
Expand Down Expand Up @@ -41,6 +41,12 @@ impl PollFd {
}
}

impl AsRawFd for PollFd {
fn as_raw_fd(&self) -> RawFd {
self.pollfd.fd
}
}

libc_bitflags! {
/// These flags define the different events that can be monitored by `poll` and `ppoll`
pub struct PollFlags: libc::c_short {
Expand Down
35 changes: 35 additions & 0 deletions src/sys/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,13 @@ pub enum ControlMessageOwned {
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
RxqOvfl(u32),

/// Socket error queue control messages read with the `MSG_ERRQUEUE` flag.
#[cfg(any(target_os = "android", target_os = "linux"))]
Ipv4RecvErr(libc::sock_extended_err, Option<sockaddr_in>),
/// Socket error queue control messages read with the `MSG_ERRQUEUE` flag.
#[cfg(any(target_os = "android", target_os = "linux"))]
Ipv6RecvErr(libc::sock_extended_err, Option<sockaddr_in6>),

/// Catch-all variant for unimplemented cmsg types.
#[doc(hidden)]
Unknown(UnknownCmsg),
Expand Down Expand Up @@ -756,13 +763,41 @@ impl ControlMessageOwned {
let drop_counter = ptr::read_unaligned(p as *const u32);
ControlMessageOwned::RxqOvfl(drop_counter)
},
#[cfg(any(target_os = "android", target_os = "linux"))]
(libc::IPPROTO_IP, libc::IP_RECVERR) => {
let (err, addr) = Self::recv_err_helper::<sockaddr_in>(p, len);
ControlMessageOwned::Ipv4RecvErr(err, addr)
},
#[cfg(any(target_os = "android", target_os = "linux"))]
(libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => {
let (err, addr) = Self::recv_err_helper::<sockaddr_in6>(p, len);
ControlMessageOwned::Ipv6RecvErr(err, addr)
},
(_, _) => {
let sl = slice::from_raw_parts(p, len);
let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(sl));
ControlMessageOwned::Unknown(ucmsg)
}
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
unsafe fn recv_err_helper<T>(p: *mut libc::c_uchar, len: usize) -> (libc::sock_extended_err, Option<T>) {
let ee = p as *const libc::sock_extended_err;
let err = ptr::read_unaligned(ee);

// For errors originating on the network, SO_EE_OFFENDER(ee) points inside the p[..len]
// CMSG_DATA buffer. For local errors, there is no address included in the control
// message, and SO_EE_OFFENDER(ee) points beyond the end of the buffer. So, we need to
// validate that the address object is in-bounds before we attempt to copy it.
let addrp = libc::SO_EE_OFFENDER(ee) as *const T;

if addrp.offset(1) as usize - (p as usize) > len {
(err, None)
} else {
(err, Some(ptr::read_unaligned(addrp)))
}
}
}

/// A type-safe zero-copy wrapper around a single control message, as used wih
Expand Down
4 changes: 4 additions & 0 deletions src/sys/socket/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ sockopt_impl!(Both, UdpGroSegment, libc::IPPROTO_UDP, libc::UDP_GRO, bool);
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
sockopt_impl!(Both, RxqOvfl, libc::SOL_SOCKET, libc::SO_RXQ_OVFL, libc::c_int);
sockopt_impl!(Both, Ipv6V6Only, libc::IPPROTO_IPV6, libc::IPV6_V6ONLY, bool);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(Both, Ipv4RecvErr, libc::IPPROTO_IP, libc::IP_RECVERR, bool);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(Both, Ipv6RecvErr, libc::IPPROTO_IPV6, libc::IPV6_RECVERR, bool);

#[cfg(any(target_os = "android", target_os = "linux"))]
#[derive(Copy, Clone, Debug)]
Expand Down
163 changes: 163 additions & 0 deletions test/sys/test_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ pub fn test_af_alg_cipher() {
#[test]
pub fn test_af_alg_aead() {
use libc::{ALG_OP_DECRYPT, ALG_OP_ENCRYPT};
use nix::fcntl::{fcntl, FcntlArg, OFlag};
use nix::sys::uio::IoVec;
use nix::unistd::{read, close};
use nix::sys::socket::{socket, sendmsg, bind, accept, setsockopt,
Expand Down Expand Up @@ -790,6 +791,11 @@ pub fn test_af_alg_aead() {

// allocate buffer for decrypted data
let mut decrypted = vec![0u8; payload_len + (assoc_size as usize) + auth_size];
// Starting with kernel 4.9, the interface changed slightly such that the
// authentication tag memory is only needed in the output buffer for encryption
// and in the input buffer for decryption.
// Do not block on read, as we may have fewer bytes than buffer size
fcntl(session_socket,FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("fcntl non_blocking");
let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt");

assert!(num_bytes >= payload_len + (assoc_size as usize));
Expand Down Expand Up @@ -1789,3 +1795,160 @@ fn test_recvmsg_rxq_ovfl() {
nix::unistd::close(in_socket).unwrap();
nix::unistd::close(out_socket).unwrap();
}

#[cfg(any(
target_os = "linux",
target_os = "android",
))]
mod linux_errqueue {
use nix::sys::socket::*;
use super::{FromStr, SocketAddr};

// Send a UDP datagram to a bogus destination address and observe an ICMP error (v4).
//
// Disable the test on QEMU because QEMU emulation of IP_RECVERR is broken (as documented on PR
// #1514).
#[cfg_attr(qemu, ignore)]
#[test]
fn test_recverr_v4() {
#[repr(u8)]
enum IcmpTypes {
DestUnreach = 3, // ICMP_DEST_UNREACH
}
#[repr(u8)]
enum IcmpUnreachCodes {
PortUnreach = 3, // ICMP_PORT_UNREACH
}

test_recverr_impl::<sockaddr_in, _, _>(
"127.0.0.1:6800",
AddressFamily::Inet,
sockopt::Ipv4RecvErr,
libc::SO_EE_ORIGIN_ICMP,
IcmpTypes::DestUnreach as u8,
IcmpUnreachCodes::PortUnreach as u8,
// Closure handles protocol-specific testing and returns generic sock_extended_err for
// protocol-independent test impl.
|cmsg| {
if let ControlMessageOwned::Ipv4RecvErr(ext_err, err_addr) = cmsg {
if let Some(origin) = err_addr {
// Validate that our network error originated from 127.0.0.1:0.
assert_eq!(origin.sin_family, AddressFamily::Inet as _);
assert_eq!(Ipv4Addr(origin.sin_addr), Ipv4Addr::new(127, 0, 0, 1));
assert_eq!(origin.sin_port, 0);
} else {
panic!("Expected some error origin");
}
return *ext_err
} else {
panic!("Unexpected control message {:?}", cmsg);
}
},
)
}

// Essentially the same test as v4.
//
// Disable the test on QEMU because QEMU emulation of IPV6_RECVERR is broken (as documented on
// PR #1514).
#[cfg_attr(qemu, ignore)]
#[test]
fn test_recverr_v6() {
#[repr(u8)]
enum IcmpV6Types {
DestUnreach = 1, // ICMPV6_DEST_UNREACH
}
#[repr(u8)]
enum IcmpV6UnreachCodes {
PortUnreach = 4, // ICMPV6_PORT_UNREACH
}

test_recverr_impl::<sockaddr_in6, _, _>(
"[::1]:6801",
AddressFamily::Inet6,
sockopt::Ipv6RecvErr,
libc::SO_EE_ORIGIN_ICMP6,
IcmpV6Types::DestUnreach as u8,
IcmpV6UnreachCodes::PortUnreach as u8,
// Closure handles protocol-specific testing and returns generic sock_extended_err for
// protocol-independent test impl.
|cmsg| {
if let ControlMessageOwned::Ipv6RecvErr(ext_err, err_addr) = cmsg {
if let Some(origin) = err_addr {
// Validate that our network error originated from localhost:0.
assert_eq!(origin.sin6_family, AddressFamily::Inet6 as _);
assert_eq!(
Ipv6Addr(origin.sin6_addr),
Ipv6Addr::from_std(&"::1".parse().unwrap()),
);
assert_eq!(origin.sin6_port, 0);
} else {
panic!("Expected some error origin");
}
return *ext_err
} else {
panic!("Unexpected control message {:?}", cmsg);
}
},
)
}

fn test_recverr_impl<SA, OPT, TESTF>(sa: &str,
af: AddressFamily,
opt: OPT,
ee_origin: u8,
ee_type: u8,
ee_code: u8,
testf: TESTF)
where
OPT: SetSockOpt<Val = bool>,
TESTF: FnOnce(&ControlMessageOwned) -> libc::sock_extended_err,
{
use nix::errno::Errno;
use nix::sys::uio::IoVec;

const MESSAGE_CONTENTS: &str = "ABCDEF";

let sock_addr = {
let std_sa = SocketAddr::from_str(sa).unwrap();
let inet_addr = InetAddr::from_std(&std_sa);
SockAddr::new_inet(inet_addr)
};
let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None).unwrap();
setsockopt(sock, opt, &true).unwrap();
if let Err(e) = sendto(sock, MESSAGE_CONTENTS.as_bytes(), &sock_addr, MsgFlags::empty()) {
assert_eq!(e, Errno::EADDRNOTAVAIL);
println!("{:?} not available, skipping test.", af);
return;
}

let mut buf = [0u8; 8];
let iovec = [IoVec::from_mut_slice(&mut buf)];
let mut cspace = cmsg_space!(libc::sock_extended_err, SA);

let msg = recvmsg(sock, &iovec, Some(&mut cspace), MsgFlags::MSG_ERRQUEUE).unwrap();
// The sent message / destination associated with the error is returned:
assert_eq!(msg.bytes, MESSAGE_CONTENTS.as_bytes().len());
assert_eq!(&buf[..msg.bytes], MESSAGE_CONTENTS.as_bytes());
// recvmsg(2): "The original destination address of the datagram that caused the error is
// supplied via msg_name;" however, this is not literally true. E.g., an earlier version
// of this test used 0.0.0.0 (::0) as the destination address, which was mutated into
// 127.0.0.1 (::1).
assert_eq!(msg.address, Some(sock_addr));

// Check for expected control message.
let ext_err = match msg.cmsgs().next() {
Some(cmsg) => testf(&cmsg),
None => panic!("No control message"),
};

assert_eq!(ext_err.ee_errno, libc::ECONNREFUSED as u32);
assert_eq!(ext_err.ee_origin, ee_origin);
// ip(7): ee_type and ee_code are set from the type and code fields of the ICMP (ICMPv6)
// header.
assert_eq!(ext_err.ee_type, ee_type);
assert_eq!(ext_err.ee_code, ee_code);
// ip(7): ee_info contains the discovered MTU for EMSGSIZE errors.
assert_eq!(ext_err.ee_info, 0);
}
}
8 changes: 8 additions & 0 deletions test/test_poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,11 @@ fn test_ppoll() {
assert_eq!(nfds, 1);
assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
}

#[test]
fn test_pollfd_fd() {
use std::os::unix::io::AsRawFd;

let pfd = PollFd::new(0x1234, PollFlags::empty());
assert_eq!(pfd.as_raw_fd(), 0x1234);
}

0 comments on commit b06c26f

Please sign in to comment.