Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 66 additions & 13 deletions src/unix/nlas.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,79 @@
// SPDX-License-Identifier: MIT

use std::{
ffi::{CStr, OsString},
os::unix::ffi::{OsStrExt, OsStringExt},
};

use anyhow::Context;
use byteorder::{ByteOrder, NativeEndian};
use netlink_packet_utils::{
buffer,
nla::{self, DefaultNla, NlaBuffer},
parsers::{parse_string, parse_u32, parse_u8},
parsers::{parse_u32, parse_u8},
traits::{Emitable, Parseable},
DecodeError,
};

use crate::constants::*;

#[derive(Debug, Eq, PartialEq, Clone)]
pub enum UnixDiagName {
/// Filesystem pathname to which the socket was bound.
Pathname(OsString),
/// Abstract socket address to which the socket was bound.
Abstract(Vec<u8>),
}

impl<T: AsRef<[u8]>> Parseable<T> for UnixDiagName {
fn parse(buf: &T) -> Result<Self, DecodeError> {
let buf = buf.as_ref();
if let Some((0, address)) = buf.split_first() {
Ok(UnixDiagName::Abstract(address.to_owned()))
} else {
Ok(UnixDiagName::Pathname(OsString::from_vec(
CStr::from_bytes_with_nul(buf)
.context("pathname is not null-terminated")?
.to_owned()
.into(),
)))
}
}
}

impl Emitable for UnixDiagName {
fn buffer_len(&self) -> usize {
match self {
UnixDiagName::Pathname(pathname) => pathname.len() + 1,
UnixDiagName::Abstract(address) => address.len() + 1,
}
}

fn emit(&self, buffer: &mut [u8]) {
match self {
UnixDiagName::Pathname(pathname) => {
let (last, first) = buffer
.split_last_mut()
.expect("buffer should not be empty");
first.copy_from_slice(pathname.as_bytes());
*last = 0;
}
UnixDiagName::Abstract(address) => {
let (first, last) = buffer
.split_first_mut()
.expect("buffer should not be empty");
*first = 0;
last.copy_from_slice(address);
}
}
}
}

#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Nla {
/// Path to which the socket was bound. This attribute is known as
/// Name to which the socket was bound. This attribute is known as
/// `UNIX_DIAG_NAME` in the kernel.
Name(String),
Name(UnixDiagName),
/// VFS information for this socket. This attribute is known as
/// `UNIX_DIAG_VFS` in the kernel.
Vfs(Vfs),
Expand Down Expand Up @@ -246,8 +303,7 @@ impl nla::Nla for Nla {
fn value_len(&self) -> usize {
use self::Nla::*;
match *self {
// +1 because we need to append a null byte
Name(ref s) => s.as_bytes().len() + 1,
Name(ref s) => s.buffer_len(),
Vfs(_) => VFS_LEN,
Peer(_) => 4,
PendingConnections(ref v) => 4 * v.len(),
Expand All @@ -261,10 +317,7 @@ impl nla::Nla for Nla {
fn emit_value(&self, buffer: &mut [u8]) {
use self::Nla::*;
match *self {
Name(ref s) => {
buffer[..s.len()].copy_from_slice(s.as_bytes());
buffer[s.len()] = 0;
}
Name(ref s) => s.emit(buffer),
Vfs(ref value) => value.emit(buffer),
Peer(value) => NativeEndian::write_u32(buffer, value),
PendingConnections(ref values) => {
Expand Down Expand Up @@ -301,10 +354,10 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>> for Nla {
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
UNIX_DIAG_NAME => {
let err = "invalid UNIX_DIAG_NAME value";
Self::Name(parse_string(payload).context(err)?)
}
UNIX_DIAG_NAME => Self::Name(
UnixDiagName::parse(&payload)
.context("invalid UNIX_DIAG_NAME value")?,
),
UNIX_DIAG_VFS => {
let err = "invalid UNIX_DIAG_VFS value";
let buf = VfsBuffer::new_checked(payload).context(err)?;
Expand Down
4 changes: 2 additions & 2 deletions src/unix/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use smallvec::SmallVec;

use crate::{
constants::*,
unix::nlas::{MemInfo, Nla},
unix::nlas::{MemInfo, Nla, UnixDiagName},
};

pub const UNIX_RESPONSE_HEADER_LEN: usize = 16;
Expand Down Expand Up @@ -92,7 +92,7 @@ impl UnixResponse {
})
}

pub fn name(&self) -> Option<&String> {
pub fn name(&self) -> Option<&UnixDiagName> {
self.nlas.iter().find_map(|nla| {
if let Nla::Name(name) = nla {
Some(name)
Expand Down
71 changes: 67 additions & 4 deletions src/unix/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use netlink_packet_utils::traits::{Emitable, Parseable};
use crate::{
constants::*,
unix::{
nlas::Nla, ShowFlags, StateFlags, UnixRequest, UnixResponse,
UnixResponseBuffer, UnixResponseHeader,
nlas::{Nla, UnixDiagName},
ShowFlags, StateFlags, UnixRequest, UnixResponse, UnixResponseBuffer,
UnixResponseHeader,
},
};

Expand Down Expand Up @@ -39,7 +40,7 @@ lazy_static! {
cookie: [0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
},
nlas: smallvec![
Nla::Name("/tmp/.ICE-unix/1151".to_string()),
Nla::Name(UnixDiagName::Pathname("/tmp/.ICE-unix/1151".into())),
Nla::ReceiveQueueLength(0, 128),
Nla::Shutdown(0),
]
Expand Down Expand Up @@ -101,7 +102,7 @@ lazy_static! {
cookie: [0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
},
nlas: smallvec![
Nla::Name("/run/user/1000/bus".to_string()),
Nla::Name(UnixDiagName::Pathname("/run/user/1000/bus".into())),
Nla::Peer(31062),
Nla::ReceiveQueueLength(0, 0),
Nla::Shutdown(0),
Expand Down Expand Up @@ -165,3 +166,65 @@ fn emit_socket_info() {
SOCKET_INFO.emit(&mut buf);
assert_eq!(&buf[..], &SOCKET_INFO_BUF[..]);
}

lazy_static! {
static ref ABSTRACT_ADDRESS: UnixResponse = UnixResponse {
header: UnixResponseHeader {
kind: SOCK_STREAM,
state: TCP_LISTEN,
inode: 20238,
cookie: [0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
},
nlas: smallvec![
Nla::Name(UnixDiagName::Abstract(
"1c1440c5f5e2a52e/bus/systemd/\0/bus-api-user".into()
)),
Nla::ReceiveQueueLength(0, 128),
Nla::Shutdown(0),
]
};
}

#[rustfmt::skip]
static ABSTRACT_ADDRESS_BUF: [u8; 84] = [
0x01, // family: AF_UNIX
0x01, // type: SOCK_STREAM
0x0a, // state: TCP_LISTEN
0x00, // padding
0x0e, 0x4f, 0x00, 0x00, // inode number
0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cookie

// NLAs
0x30, 0x00, // length: 48
0x00, 0x00, // type: UNIX_DIAG_NAME
// value: \01c1440c5f5e2a52e/bus/systemd/\0/bus-api-user
0x00, 0x31, 0x63, 0x31, 0x34, 0x34, 0x30, 0x63, 0x35, 0x66, 0x35, 0x65, 0x32, 0x61, 0x35, 0x32, 0x65, 0x2f, 0x62, 0x75, 0x73, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x2f, 0x00, 0x2f, 0x62, 0x75, 0x73, 0x2d, 0x61, 0x70, 0x69, 0x2d, 0x75, 0x73, 0x65, 0x72,

0x0c, 0x00, // length: 12
0x04, 0x00, // type: UNIX_DIAG_RQLEN
// value: ReceiveQueueLength(0, 128)
0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00,

0x05, 0x00, // length: 5
0x06, 0x00, // type: UNIX_DIAG_SHUTDOWN
0x00, // value: 0
0x00, 0x00, 0x00 // padding
];

#[test]
fn parse_abstract_address() {
let parsed = UnixResponse::parse(
&UnixResponseBuffer::new_checked(&&ABSTRACT_ADDRESS_BUF[..]).unwrap(),
)
.unwrap();
assert_eq!(parsed, *ABSTRACT_ADDRESS);
}

#[test]
fn emit_abstract_address() {
assert_eq!(ABSTRACT_ADDRESS.buffer_len(), 84);
let mut buf = vec![0xff; ABSTRACT_ADDRESS.buffer_len()];
ABSTRACT_ADDRESS.emit(&mut buf);
assert_eq!(&buf[..], &ABSTRACT_ADDRESS_BUF[..]);
}