blob: 12ef583dd53a7b80ab0602b0d976993f957e712b [file] [edit]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::alloc::Layout;
use std::mem::MaybeUninit;
use std::os::unix::io::AsRawFd;
use std::str;
use libc::EINVAL;
use log::error;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
use zerocopy::Ref;
use super::errno_result;
use super::getpid;
use super::Error;
use super::RawDescriptor;
use super::Result;
use crate::alloc::LayoutAllocation;
use crate::descriptor::AsRawDescriptor;
use crate::descriptor::FromRawDescriptor;
use crate::descriptor::SafeDescriptor;
macro_rules! debug_pr {
// By default debugs are suppressed, to enabled them replace macro body with:
// $($args:tt)+) => (println!($($args)*))
($($args:tt)+) => {};
}
const NLMSGHDR_SIZE: usize = std::mem::size_of::<NlMsgHdr>();
const GENL_HDRLEN: usize = std::mem::size_of::<GenlMsgHdr>();
const NLA_HDRLEN: usize = std::mem::size_of::<NlAttr>();
const NLATTR_ALIGN_TO: usize = 4;
#[repr(C)]
#[derive(Copy, Clone, FromZeroes, FromBytes, AsBytes)]
struct NlMsgHdr {
pub nlmsg_len: u32,
pub nlmsg_type: u16,
pub nlmsg_flags: u16,
pub nlmsg_seq: u32,
pub nlmsg_pid: u32,
}
/// Netlink attribute struct, can be used by netlink consumer
#[repr(C)]
#[derive(Copy, Clone, FromZeroes, FromBytes, AsBytes)]
pub struct NlAttr {
pub len: u16,
pub _type: u16,
}
/// Generic netlink header struct, can be used by netlink consumer
#[repr(C)]
#[derive(Copy, Clone, FromZeroes, FromBytes, AsBytes)]
pub struct GenlMsgHdr {
pub cmd: u8,
pub version: u8,
pub reserved: u16,
}
/// A single netlink message, including its header and data.
pub struct NetlinkMessage<'a> {
pub _type: u16,
pub flags: u16,
pub seq: u32,
pub pid: u32,
pub data: &'a [u8],
}
pub struct NlAttrWithData<'a> {
pub len: u16,
pub _type: u16,
pub data: &'a [u8],
}
fn nlattr_align(offset: usize) -> usize {
(offset + NLATTR_ALIGN_TO - 1) & !(NLATTR_ALIGN_TO - 1)
}
/// Iterator over `struct NlAttr` as received from a netlink socket.
pub struct NetlinkGenericDataIter<'a> {
// `data` must be properly aligned for NlAttr.
data: &'a [u8],
}
impl<'a> Iterator for NetlinkGenericDataIter<'a> {
type Item = NlAttrWithData<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.len() < NLA_HDRLEN {
return None;
}
let nl_hdr = NlAttr::read_from(&self.data[..NLA_HDRLEN])?;
// Make sure NlAtrr fits
let nl_data_len = nl_hdr.len as usize;
if nl_data_len < NLA_HDRLEN || nl_data_len > self.data.len() {
return None;
}
// Get data related to processed NlAttr
let data_start = NLA_HDRLEN;
let data = &self.data[data_start..nl_data_len];
// Get next NlAttr
let next_hdr = nlattr_align(nl_data_len);
if next_hdr >= self.data.len() {
self.data = &[];
} else {
self.data = &self.data[next_hdr..];
}
Some(NlAttrWithData {
_type: nl_hdr._type,
len: nl_hdr.len,
data,
})
}
}
/// Iterator over `struct nlmsghdr` as received from a netlink socket.
pub struct NetlinkMessageIter<'a> {
// `data` must be properly aligned for nlmsghdr.
data: &'a [u8],
}
impl<'a> Iterator for NetlinkMessageIter<'a> {
type Item = NetlinkMessage<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.len() < NLMSGHDR_SIZE {
return None;
}
let hdr = NlMsgHdr::read_from(&self.data[..NLMSGHDR_SIZE])?;
// NLMSG_OK
let msg_len = hdr.nlmsg_len as usize;
if msg_len < NLMSGHDR_SIZE || msg_len > self.data.len() {
return None;
}
// NLMSG_DATA
let data_start = NLMSGHDR_SIZE;
let data = &self.data[data_start..msg_len];
// NLMSG_NEXT
let align_to = std::mem::align_of::<NlMsgHdr>();
let next_hdr = (msg_len + align_to - 1) & !(align_to - 1);
if next_hdr >= self.data.len() {
self.data = &[];
} else {
self.data = &self.data[next_hdr..];
}
Some(NetlinkMessage {
_type: hdr.nlmsg_type,
flags: hdr.nlmsg_flags,
seq: hdr.nlmsg_seq,
pid: hdr.nlmsg_pid,
data,
})
}
}
/// Safe wrapper for `NETLINK_GENERIC` netlink sockets.
pub struct NetlinkGenericSocket {
sock: SafeDescriptor,
}
impl AsRawDescriptor for NetlinkGenericSocket {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.sock.as_raw_descriptor()
}
}
impl NetlinkGenericSocket {
/// Create and bind a new `NETLINK_GENERIC` socket.
pub fn new(nl_groups: u32) -> Result<Self> {
// SAFETY:
// Safe because we check the return value and convert the raw fd into a SafeDescriptor.
let sock = unsafe {
let fd = libc::socket(
libc::AF_NETLINK,
libc::SOCK_RAW | libc::SOCK_CLOEXEC,
libc::NETLINK_GENERIC,
);
if fd < 0 {
return errno_result();
}
SafeDescriptor::from_raw_descriptor(fd)
};
// SAFETY:
// This MaybeUninit dance is needed because sockaddr_nl has a private padding field and
// doesn't implement Default. Safe because all 0s is valid data for sockaddr_nl.
let mut sa = unsafe { MaybeUninit::<libc::sockaddr_nl>::zeroed().assume_init() };
sa.nl_family = libc::AF_NETLINK as libc::sa_family_t;
sa.nl_groups = nl_groups;
// SAFETY:
// Safe because we pass a descriptor that we own and valid pointer/size for sockaddr.
unsafe {
let res = libc::bind(
sock.as_raw_fd(),
&sa as *const libc::sockaddr_nl as *const libc::sockaddr,
std::mem::size_of_val(&sa) as libc::socklen_t,
);
if res < 0 {
return errno_result();
}
}
Ok(NetlinkGenericSocket { sock })
}
/// Receive messages from the netlink socket.
pub fn recv(&self) -> Result<NetlinkGenericRead> {
let buf_size = 8192; // TODO(dverkamp): make this configurable?
// Create a buffer with sufficient alignment for nlmsghdr.
let layout = Layout::from_size_align(buf_size, std::mem::align_of::<NlMsgHdr>())
.map_err(|_| Error::new(EINVAL))?;
let allocation = LayoutAllocation::uninitialized(layout);
// SAFETY:
// Safe because we pass a valid, owned socket fd and a valid pointer/size for the buffer.
let bytes_read = unsafe {
let res = libc::recv(self.sock.as_raw_fd(), allocation.as_ptr(), buf_size, 0);
if res < 0 {
return errno_result();
}
res as usize
};
Ok(NetlinkGenericRead {
allocation,
len: bytes_read,
})
}
pub fn family_name_query(&self, family_name: String) -> Result<NetlinkGenericRead> {
let buf_size = 1024;
debug_pr!(
"preparing query for family name {}, len {}",
family_name,
family_name.len()
);
// Create a buffer with sufficient alignment for nlmsghdr.
let layout = Layout::from_size_align(buf_size, std::mem::align_of::<NlMsgHdr>())
.map_err(|_| Error::new(EINVAL))
.unwrap();
let mut allocation = LayoutAllocation::zeroed(layout);
// SAFETY:
// Safe because the data in allocation was initialized up to `buf_size` and is
// sufficiently aligned.
let data = unsafe { allocation.as_mut_slice(buf_size) };
// Prepare the netlink message header
let hdr = Ref::<_, NlMsgHdr>::new(&mut data[..NLMSGHDR_SIZE])
.expect("failed to unwrap")
.into_mut();
hdr.nlmsg_len = NLMSGHDR_SIZE as u32 + GENL_HDRLEN as u32;
hdr.nlmsg_len += NLA_HDRLEN as u32 + family_name.len() as u32 + 1;
hdr.nlmsg_flags = libc::NLM_F_REQUEST as u16;
hdr.nlmsg_type = libc::GENL_ID_CTRL as u16;
hdr.nlmsg_pid = getpid() as u32;
// Prepare generic netlink message header
let genl_hdr_end = NLMSGHDR_SIZE + GENL_HDRLEN;
let genl_hdr = Ref::<_, GenlMsgHdr>::new(&mut data[NLMSGHDR_SIZE..genl_hdr_end])
.expect("unable to get GenlMsgHdr from slice")
.into_mut();
genl_hdr.cmd = libc::CTRL_CMD_GETFAMILY as u8;
genl_hdr.version = 0x1;
// Netlink attributes
let nlattr_start = genl_hdr_end;
let nlattr_end = nlattr_start + NLA_HDRLEN;
let nl_attr = Ref::<_, NlAttr>::new(&mut data[nlattr_start..nlattr_end])
.expect("unable to get NlAttr from slice")
.into_mut();
nl_attr._type = libc::CTRL_ATTR_FAMILY_NAME as u16;
nl_attr.len = family_name.len() as u16 + 1 + NLA_HDRLEN as u16;
// Fill the message payload with the family name
let payload_start = nlattr_end;
let payload_end = payload_start + family_name.len();
data[payload_start..payload_end].copy_from_slice(family_name.as_bytes());
// SAFETY:
// Safe because we pass a valid, owned socket fd and a valid pointer/size for the buffer.
unsafe {
let res = libc::send(
self.sock.as_raw_fd(),
allocation.as_ptr(),
payload_end + 1,
0,
);
if res < 0 {
error!("failed to send get_family_cmd");
return errno_result();
}
};
// Return the answer
match self.recv() {
Ok(msg) => Ok(msg),
Err(e) => {
error!("recv get_family returned with error {}", e);
Err(e)
}
}
}
}
fn parse_ctrl_group_name_and_id(
nested_nl_attr_data: NetlinkGenericDataIter,
group_name: &str,
) -> Option<u32> {
let mut mcast_group_id: Option<u32> = None;
for nested_nl_attr in nested_nl_attr_data {
debug_pr!(
"\t\tmcast_grp: nlattr type {}, len {}",
nested_nl_attr._type,
nested_nl_attr.len
);
if nested_nl_attr._type == libc::CTRL_ATTR_MCAST_GRP_ID as u16 {
mcast_group_id = Some(u32::from_ne_bytes(nested_nl_attr.data.try_into().unwrap()));
debug_pr!("\t\t mcast group_id {}", mcast_group_id?);
}
if nested_nl_attr._type == libc::CTRL_ATTR_MCAST_GRP_NAME as u16 {
debug_pr!(
"\t\t mcast group name {}",
strip_padding(&nested_nl_attr.data)
);
// If the group name match and the group_id was set in previous iteration, return,
// valid for group_name, group_id
if group_name.eq(strip_padding(nested_nl_attr.data)) && mcast_group_id.is_some() {
debug_pr!(
"\t\t Got what we were looking for group_id = {} for {}",
mcast_group_id?,
group_name
);
return mcast_group_id;
}
}
}
None
}
/// Parse CTRL_ATTR_MCAST_GROUPS data in order to get multicast group id
///
/// On success, returns group_id for a given `group_name`
///
/// # Arguments
///
/// * `nl_attr_area`
///
/// Nested attributes area (CTRL_ATTR_MCAST_GROUPS data), where nl_attr's corresponding to
/// specific groups are embed
///
/// * `group_name`
///
/// String with group_name for which we are looking group_id
///
/// the CTRL_ATTR_MCAST_GROUPS data has nested attributes. Each of nested attribute is per
/// multicast group attributes, which have another nested attributes: CTRL_ATTR_MCAST_GRP_NAME and
/// CTRL_ATTR_MCAST_GRP_ID. Need to parse all of them to get mcast group id for a given group_name..
///
/// Illustrated layout:
/// CTRL_ATTR_MCAST_GROUPS:
/// GR1 (nl_attr._type = 1):
/// CTRL_ATTR_MCAST_GRP_ID,
/// CTRL_ATTR_MCAST_GRP_NAME,
/// GR2 (nl_attr._type = 2):
/// CTRL_ATTR_MCAST_GRP_ID,
/// CTRL_ATTR_MCAST_GRP_NAME,
/// ..
///
/// Unfortunately kernel implementation uses `nla_nest_start_noflag` for that
/// purpose, which means that it never marked their nest attributes with NLA_F_NESTED flag.
/// Therefore all this nesting stages need to be deduced based on specific nl_attr type.
fn parse_ctrl_mcast_group_id(
nl_attr_area: NetlinkGenericDataIter,
group_name: &str,
) -> Option<u32> {
// There may be multiple nested multicast groups, go through all of them.
// Each of nested group, has other nested nlattr:
// CTRL_ATTR_MCAST_GRP_ID
// CTRL_ATTR_MCAST_GRP_NAME
//
// which are further proceed by parse_ctrl_group_name_and_id
for nested_gr_nl_attr in nl_attr_area {
debug_pr!(
"\tmcast_groups: nlattr type(gr_nr) {}, len {}",
nested_gr_nl_attr._type,
nested_gr_nl_attr.len
);
let netlink_nested_attr = NetlinkGenericDataIter {
data: nested_gr_nl_attr.data,
};
if let Some(mcast_group_id) = parse_ctrl_group_name_and_id(netlink_nested_attr, group_name)
{
return Some(mcast_group_id);
}
}
None
}
// Like `CStr::from_bytes_with_nul` but strips any bytes starting from first '\0'-byte and
// returns &str. Panics if `b` doesn't contain any '\0' bytes.
fn strip_padding(b: &[u8]) -> &str {
// It would be nice if we could use memchr here but that's locked behind an unstable gate.
let pos = b
.iter()
.position(|&c| c == 0)
.expect("`b` doesn't contain any nul bytes");
str::from_utf8(&b[..pos]).unwrap()
}
pub struct NetlinkGenericRead {
allocation: LayoutAllocation,
len: usize,
}
impl NetlinkGenericRead {
pub fn iter(&self) -> NetlinkMessageIter {
// SAFETY:
// Safe because the data in allocation was initialized up to `self.len` by `recv()` and is
// sufficiently aligned.
let data = unsafe { &self.allocation.as_slice(self.len) };
NetlinkMessageIter { data }
}
/// Parse NetlinkGeneric response in order to get multicast group id
///
/// On success, returns group_id for a given `group_name`
///
/// # Arguments
///
/// * `group_name` - String with group_name for which we are looking group_id
///
/// Response from family_name_query (CTRL_CMD_GETFAMILY) is a netlink message with multiple
/// attributes encapsulated (some of them are nested). An example response layout is
/// illustrated below:
///
/// {
/// CTRL_ATTR_FAMILY_NAME
/// CTRL_ATTR_FAMILY_ID
/// CTRL_ATTR_VERSION
/// ...
/// CTRL_ATTR_MCAST_GROUPS {
/// GR1 (nl_attr._type = 1) {
/// CTRL_ATTR_MCAST_GRP_ID *we need parse this attr to obtain group id used for
/// the group mask
/// CTRL_ATTR_MCAST_GRP_NAME *group_name that we need to match with
/// }
/// GR2 (nl_attr._type = 2) {
/// CTRL_ATTR_MCAST_GRP_ID
/// CTRL_ATTR_MCAST_GRP_NAME
/// }
/// ...
/// }
/// }
pub fn get_multicast_group_id(&self, group_name: String) -> Option<u32> {
for netlink_msg in self.iter() {
debug_pr!(
"received type: {}, flags {}, pid {}, data {:?}",
netlink_msg._type,
netlink_msg.flags,
netlink_msg.pid,
netlink_msg.data
);
if netlink_msg._type != libc::GENL_ID_CTRL as u16 {
error!("Received not a generic netlink controller msg");
return None;
}
let netlink_data = NetlinkGenericDataIter {
data: &netlink_msg.data[GENL_HDRLEN..],
};
for nl_attr in netlink_data {
debug_pr!("nl_attr type {}, len {}", nl_attr._type, nl_attr.len);
if nl_attr._type == libc::CTRL_ATTR_MCAST_GROUPS as u16 {
let netlink_nested_attr = NetlinkGenericDataIter { data: nl_attr.data };
if let Some(mcast_group_id) =
parse_ctrl_mcast_group_id(netlink_nested_attr, &group_name)
{
return Some(mcast_group_id);
}
}
}
}
None
}
}