Skip to content

Commit

Permalink
Add new linux symbol for multicast-all
Browse files Browse the repository at this point in the history
unistd: Increase maximum passwd/group buffer to 1MB

We have one UNIX group that contains most of our users whose size is
about 20 kB, so `Group::from_name` is failing with ERANGE.

The discussion on PR nix-rust#864 suggests that 1 MB is a reasonable maximum -
it follows what FreeBSD's libc does. (glibc appears to have no maximum
on the _r function and will just double the buffer until malloc fails,
but that's not particularly Rusty.)

Use memoffset::offset_of instead of homegrown macro

The homegrown macro was fine in 2016, but at some point it technically
became UB.  The memoffset crate does the same thing, but avoids UB when
using rustc 1.51.0 or later.

Fixes nix-rust#1415

Check all tests in CI

Travis didn't compile check tests on platforms that couldn't run tests
in CI, so they bitrotted.  Let's see how bad they are.

Most annoyingly, 32-bit Android defines mode_t as 16 bits, but
stat.st_mode as 32-bits.

Fix spurious errors using `sendmmsg` with multiple cmsgs

Before this fix, the buffer that holds cmsgs may move due to the resize()
call. That causes msg_hdr pointing to invalid memory, which ends up
breaking the sendmmsg() call, resulting in EINVAL.

This change fixes it by avoiding re-allocating the buffers.

Support TIMESTAMPNS for linux
  • Loading branch information
anth1y committed Apr 17, 2021
1 parent 6af11c1 commit 7d451b0
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 85 deletions.
7 changes: 2 additions & 5 deletions .cirrus.yml
Expand Up @@ -22,6 +22,7 @@ task:
- cargo test --target i686-unknown-freebsd
before_cache_script: rm -rf $CARGO_HOME/registry/index

# Test OSX and iOS in a full VM
task:
matrix:
- name: OSX x86_64
Expand Down Expand Up @@ -162,24 +163,20 @@ task:
- name: Linux x32
env:
TARGET: x86_64-unknown-linux-gnux32
CHECK_TESTS: true
- name: NetBSD x86_64
env:
TARGET: x86_64-unknown-netbsd
- name: Fuchsia x86_64
env:
TARGET: x86_64-fuchsia
CHECK_TESTS: true
container:
image: rust:1.40
setup_script:
- rustup target add $TARGET
script:
- cargo +$TOOLCHAIN check --target $TARGET
- cargo +$TOOLCHAIN check --target $TARGET --release
- 'if [ "$CHECK_TESTS" == true ]; then cargo +$TOOLCHAIN check --all-targets --target $TARGET; fi'
# TODO: check the tests, too. The old Travis CI setup didn't do that, so
# they don't build on all platforms.
- cargo +$TOOLCHAIN check --all-targets --target $TARGET
before_cache_script: rm -rf $CARGO_HOME/registry/index

# illumos toolchain isn't available via rustup until 1.50
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased] - ReleaseDate
### Added
- Add new symbol for linux IP_MULTICAST_ALL
(#[]())
- Added TIMESTAMPNS support for linux
(#[1402](https://github.com/nix-rust/nix/pull/1402))

### Changed
- Made `forkpty` unsafe, like `fork`
Expand All @@ -13,6 +17,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Allow `sockaddr_ll` size, as reported by the Linux kernel, to be smaller then it's definition
(#[1395](https://github.com/nix-rust/nix/pull/1395))
- Fix spurious errors using `sendmmsg` with multiple cmsgs
(#[1414](https://github.com/nix-rust/nix/pull/1414))

### Removed

Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Expand Up @@ -32,10 +32,13 @@ targets = [
]

[dependencies]
libc = { version = "0.2.82", features = [ "extra_traits" ] }
libc = { version = "0.2.93", features = [ "extra_traits" ] }
bitflags = "1.1"
cfg-if = "1.0"

[target.'cfg(not(target_os = "redox"))'.dependencies]
memoffset = "0.6.3"

[target.'cfg(target_os = "dragonfly")'.build-dependencies]
cc = "1"

Expand Down
13 changes: 0 additions & 13 deletions src/macros.rs
Expand Up @@ -203,16 +203,3 @@ macro_rules! libc_enum {
}
};
}

/// A Rust version of the familiar C `offset_of` macro. It returns the byte
/// offset of `field` within struct `ty`
#[cfg(not(target_os = "redox"))]
macro_rules! offset_of {
($ty:ty, $field:ident) => {{
// Safe because we don't actually read from the dereferenced pointer
#[allow(unused_unsafe)] // for when the macro is used in an unsafe block
unsafe {
&(*(ptr::null() as *const $ty)).$field as *const _ as usize
}
}}
}
1 change: 1 addition & 0 deletions src/sys/socket/addr.rs
@@ -1,6 +1,7 @@
use super::sa_family_t;
use crate::{Error, Result, NixPath};
use crate::errno::Errno;
use memoffset::offset_of;
use std::{fmt, mem, net, ptr, slice};
use std::ffi::OsStr;
use std::hash::{Hash, Hasher};
Expand Down
24 changes: 18 additions & 6 deletions src/sys/socket/mod.rs
Expand Up @@ -5,8 +5,11 @@ use cfg_if::cfg_if;
use crate::{Error, Result, errno::Errno};
use libc::{self, c_void, c_int, iovec, socklen_t, size_t,
CMSG_FIRSTHDR, CMSG_NXTHDR, CMSG_DATA, CMSG_LEN};
use memoffset::offset_of;
use std::{mem, ptr, slice};
use std::os::unix::io::RawFd;
#[cfg(all(target_os = "linux"))]
use crate::sys::time::TimeSpec;
use crate::sys::time::TimeVal;
use crate::sys::uio::IoVec;

Expand Down Expand Up @@ -554,6 +557,11 @@ pub enum ControlMessageOwned {
/// # }
/// ```
ScmTimestamp(TimeVal),
/// Nanoseconds resolution timestamp
///
/// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html)
#[cfg(all(target_os = "linux"))]
ScmTimestampns(TimeSpec),
#[cfg(any(
target_os = "android",
target_os = "ios",
Expand Down Expand Up @@ -645,6 +653,11 @@ impl ControlMessageOwned {
let tv: libc::timeval = ptr::read_unaligned(p as *const _);
ControlMessageOwned::ScmTimestamp(TimeVal::from(tv))
},
#[cfg(all(target_os = "linux"))]
(libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => {
let ts: libc::timespec = ptr::read_unaligned(p as *const _);
ControlMessageOwned::ScmTimestampns(TimeSpec::from(ts))
}
#[cfg(any(
target_os = "android",
target_os = "freebsd",
Expand Down Expand Up @@ -1101,23 +1114,22 @@ pub fn sendmmsg<'a, I, C>(

let mut output = Vec::<libc::mmsghdr>::with_capacity(reserve_items);

let mut cmsgs_buffer = vec![0u8; 0];
let mut cmsgs_buffers = Vec::<Vec<u8>>::with_capacity(reserve_items);

for d in iter {
let cmsgs_start = cmsgs_buffer.len();
let cmsgs_required_capacity: usize = d.cmsgs.as_ref().iter().map(|c| c.space()).sum();
let cmsgs_buffer_need_capacity = cmsgs_start + cmsgs_required_capacity;
cmsgs_buffer.resize(cmsgs_buffer_need_capacity, 0);
let capacity: usize = d.cmsgs.as_ref().iter().map(|c| c.space()).sum();
let mut cmsgs_buffer = vec![0u8; capacity];

output.push(libc::mmsghdr {
msg_hdr: pack_mhdr_to_send(
&mut cmsgs_buffer[cmsgs_start..],
&mut cmsgs_buffer,
&d.iov,
&d.cmsgs,
d.addr.as_ref()
),
msg_len: 0,
});
cmsgs_buffers.push(cmsgs_buffer);
};

let ret = unsafe { libc::sendmmsg(fd, output.as_mut_ptr(), output.len() as _, flags.bits() as _) };
Expand Down
4 changes: 4 additions & 0 deletions src/sys/socket/sockopt.rs
Expand Up @@ -238,6 +238,8 @@ cfg_if! {
}
sockopt_impl!(Both, IpMulticastTtl, libc::IPPROTO_IP, libc::IP_MULTICAST_TTL, u8);
sockopt_impl!(Both, IpMulticastLoop, libc::IPPROTO_IP, libc::IP_MULTICAST_LOOP, bool);
[cfg(any(target_os = "linux", target_os = "android"))]
sockopt_impl!(Both, IpMulticastAll, libc::IPPROTO_IP, libc::IP_MULTICAST_ALL, bool);
sockopt_impl!(Both, ReceiveTimeout, libc::SOL_SOCKET, libc::SO_RCVTIMEO, TimeVal);
sockopt_impl!(Both, SendTimeout, libc::SOL_SOCKET, libc::SO_SNDTIMEO, TimeVal);
sockopt_impl!(Both, Broadcast, libc::SOL_SOCKET, libc::SO_BROADCAST, bool);
Expand Down Expand Up @@ -272,6 +274,8 @@ sockopt_impl!(Both, BindToDevice, libc::SOL_SOCKET, libc::SO_BINDTODEVICE, OsStr
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(GetOnly, OriginalDst, libc::SOL_IP, libc::SO_ORIGINAL_DST, libc::sockaddr_in);
sockopt_impl!(Both, ReceiveTimestamp, libc::SOL_SOCKET, libc::SO_TIMESTAMP, bool);
#[cfg(all(target_os = "linux"))]
sockopt_impl!(Both, ReceiveTimestampns, libc::SOL_SOCKET, libc::SO_TIMESTAMPNS, bool);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(Both, IpTransparent, libc::SOL_IP, libc::IP_TRANSPARENT, bool);
#[cfg(target_os = "openbsd")]
Expand Down
8 changes: 4 additions & 4 deletions src/unistd.rs
Expand Up @@ -2641,10 +2641,10 @@ impl User {
libc::size_t,
*mut *mut libc::passwd) -> libc::c_int
{
let buflimit = 16384;
let buflimit = 1048576;
let bufsize = match sysconf(SysconfVar::GETPW_R_SIZE_MAX) {
Ok(Some(n)) => n as usize,
Ok(None) | Err(_) => buflimit as usize,
Ok(None) | Err(_) => 16384,
};

let mut cbuf = Vec::with_capacity(bufsize);
Expand Down Expand Up @@ -2762,10 +2762,10 @@ impl Group {
libc::size_t,
*mut *mut libc::group) -> libc::c_int
{
let buflimit = 16384;
let buflimit = 1048576;
let bufsize = match sysconf(SysconfVar::GETGR_R_SIZE_MAX) {
Ok(Some(n)) => n as usize,
Ok(None) | Err(_) => buflimit as usize,
Ok(None) | Err(_) => 16384,
};

let mut cbuf = Vec::with_capacity(bufsize);
Expand Down
2 changes: 1 addition & 1 deletion test/sys/mod.rs
Expand Up @@ -41,5 +41,5 @@ mod test_pthread;
target_os = "netbsd",
target_os = "openbsd"))]
mod test_ptrace;
#[cfg(any(target_os = "android", target_os = "linux"))]
#[cfg(target_os = "linux")]
mod test_timerfd;
133 changes: 126 additions & 7 deletions test/sys/test_socket.rs
Expand Up @@ -757,6 +757,7 @@ pub fn test_af_alg_aead() {
target_os = "netbsd"))]
#[test]
pub fn test_sendmsg_ipv4packetinfo() {
use cfg_if::cfg_if;
use nix::sys::uio::IoVec;
use nix::sys::socket::{socket, sendmsg, bind,
AddressFamily, SockType, SockFlag, SockAddr,
Expand All @@ -778,11 +779,21 @@ pub fn test_sendmsg_ipv4packetinfo() {
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,
};
cfg_if! {
if #[cfg(target_os = "netbsd")] {
drop(sin);
let pi = libc::in_pktinfo {
ipi_ifindex: 0, /* Unspecified interface */
ipi_addr: libc::in_addr { s_addr: 0 },
};
} else {
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)];

Expand Down Expand Up @@ -1472,13 +1483,13 @@ pub fn test_recv_ipv6pktinfo() {
Some(ControlMessageOwned::Ipv6PacketInfo(pktinfo)) => {
let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex");
assert_eq!(
pktinfo.ipi6_ifindex,
pktinfo.ipi6_ifindex as libc::c_uint,
i,
"unexpected ifindex (expected {}, got {})",
i,
pktinfo.ipi6_ifindex
);
}
},
_ => (),
}
assert!(cmsgs.next().is_none(), "unexpected additional control msg");
Expand Down Expand Up @@ -1535,3 +1546,111 @@ pub fn test_vsock() {
close(s1).unwrap();
thr.join().unwrap();
}

// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU
// support is suspected.
#[cfg_attr(not(any(target_arch = "x86_64")), ignore)]
#[cfg(all(target_os = "linux"))]
#[test]
fn test_recvmsg_timestampns() {
use nix::sys::socket::*;
use nix::sys::uio::IoVec;
use nix::sys::time::*;
use std::time::*;

// Set up
let message = "Ohayō!".as_bytes();
let in_socket = socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None).unwrap();
setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap();
let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0);
bind(in_socket, &SockAddr::new_inet(localhost)).unwrap();
let address = getsockname(in_socket).unwrap();
// Get initial time
let time0 = SystemTime::now();
// Send the message
let iov = [IoVec::from_slice(message)];
let flags = MsgFlags::empty();
let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap();
assert_eq!(message.len(), l);
// Receive the message
let mut buffer = vec![0u8; message.len()];
let mut cmsgspace = nix::cmsg_space!(TimeSpec);
let iov = [IoVec::from_mut_slice(&mut buffer)];
let r = recvmsg(in_socket, &iov, Some(&mut cmsgspace), flags).unwrap();
let rtime = match r.cmsgs().next() {
Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime,
Some(_) => panic!("Unexpected control message"),
None => panic!("No control message")
};
// Check the final time
let time1 = SystemTime::now();
// the packet's received timestamp should lie in-between the two system
// times, unless the system clock was adjusted in the meantime.
let rduration = Duration::new(rtime.tv_sec() as u64,
rtime.tv_nsec() as u32);
assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration);
assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap());
// Close socket
nix::unistd::close(in_socket).unwrap();
}

// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU
// support is suspected.
#[cfg_attr(not(any(target_arch = "x86_64")), ignore)]
#[cfg(all(target_os = "linux"))]
#[test]
fn test_recvmmsg_timestampns() {
use nix::sys::socket::*;
use nix::sys::uio::IoVec;
use nix::sys::time::*;
use std::time::*;

// Set up
let message = "Ohayō!".as_bytes();
let in_socket = socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None).unwrap();
setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap();
let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0);
bind(in_socket, &SockAddr::new_inet(localhost)).unwrap();
let address = getsockname(in_socket).unwrap();
// Get initial time
let time0 = SystemTime::now();
// Send the message
let iov = [IoVec::from_slice(message)];
let flags = MsgFlags::empty();
let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap();
assert_eq!(message.len(), l);
// Receive the message
let mut buffer = vec![0u8; message.len()];
let mut cmsgspace = nix::cmsg_space!(TimeSpec);
let iov = [IoVec::from_mut_slice(&mut buffer)];
let mut data = vec![
RecvMmsgData {
iov,
cmsg_buffer: Some(&mut cmsgspace),
},
];
let r = recvmmsg(in_socket, &mut data, flags, None).unwrap();
let rtime = match r[0].cmsgs().next() {
Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime,
Some(_) => panic!("Unexpected control message"),
None => panic!("No control message")
};
// Check the final time
let time1 = SystemTime::now();
// the packet's received timestamp should lie in-between the two system
// times, unless the system clock was adjusted in the meantime.
let rduration = Duration::new(rtime.tv_sec() as u64,
rtime.tv_nsec() as u32);
assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration);
assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap());
// Close socket
nix::unistd::close(in_socket).unwrap();
}

0 comments on commit 7d451b0

Please sign in to comment.