use neli::{
attr::Attribute,
consts::{
nl::{NlType, NlmF, NlmFFlags},
rtnl::{Arphrd, RtAddrFamily, Rtm},
rtnl::{Iff, IffFlags, Ifla, IflaInfo},
socket::NlFamily,
},
err::NlError,
nl::{NlPayload, Nlmsghdr},
rtnl::{Ifinfomsg, Rtattr},
socket::NlSocketHandle,
types::{Buffer, RtBuffer},
FromBytes, ToBytes,
};
use nix::{self, net::if_::if_nametoindex, unistd};
use rt::IflaCan;
use std::{
ffi::CStr,
fmt::Debug,
os::raw::{c_int, c_uint},
};
mod rt;
use rt::can_ctrlmode;
pub use rt::CanState;
type NlResult<T> = Result<T, NlError>;
type NlInfoError = NlError<Rtm, Ifinfomsg>;
pub type CanBitTiming = rt::can_bittiming;
pub type CanBitTimingConst = rt::can_bittiming_const;
pub type CanClock = rt::can_clock;
pub type CanBerrCounter = rt::can_berr_counter;
#[allow(missing_copy_implementations)]
#[derive(Debug, Default, Clone)]
pub struct InterfaceDetails {
pub name: Option<String>,
pub index: c_uint,
pub is_up: bool,
pub mtu: Option<Mtu>,
pub can: InterfaceCanParams,
}
impl InterfaceDetails {
pub fn new(index: c_uint) -> Self {
Self {
index,
..Self::default()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Mtu {
Standard = 16,
Fd = 72,
}
impl TryFrom<u32> for Mtu {
type Error = std::io::Error;
fn try_from(val: u32) -> Result<Self, Self::Error> {
match val {
16 => Ok(Mtu::Standard),
72 => Ok(Mtu::Fd),
_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidData)),
}
}
}
#[allow(missing_copy_implementations)]
#[derive(Debug, Default, Clone)]
pub struct InterfaceCanParams {
pub bit_timing: Option<CanBitTiming>,
pub bit_timing_const: Option<CanBitTimingConst>,
pub clock: Option<CanClock>,
pub state: Option<CanState>,
pub restart_ms: Option<u32>,
pub berr_counter: Option<CanBerrCounter>,
pub ctrl_mode: Option<CanCtrlModes>,
pub data_bit_timing: Option<CanBitTiming>,
pub data_bit_timing_const: Option<CanBitTimingConst>,
pub termination: Option<u16>,
}
impl TryFrom<&Rtattr<Ifla, Buffer>> for InterfaceCanParams {
type Error = NlInfoError;
fn try_from(link_info: &Rtattr<Ifla, Buffer>) -> Result<Self, Self::Error> {
let mut params = Self::default();
for info in link_info.get_attr_handle::<IflaInfo>()?.get_attrs() {
if info.rta_type == IflaInfo::Data {
for attr in info.get_attr_handle::<IflaCan>()?.get_attrs() {
match attr.rta_type {
IflaCan::BitTiming => {
params.bit_timing = Some(attr.get_payload_as::<CanBitTiming>()?);
}
IflaCan::BitTimingConst => {
params.bit_timing_const =
Some(attr.get_payload_as::<CanBitTimingConst>()?);
}
IflaCan::Clock => {
params.clock = Some(attr.get_payload_as::<CanClock>()?);
}
IflaCan::State => {
params.state = CanState::try_from(attr.get_payload_as::<u32>()?).ok();
}
IflaCan::CtrlMode => {
let ctrl_mode = attr.get_payload_as::<can_ctrlmode>()?;
params.ctrl_mode = Some(CanCtrlModes(ctrl_mode));
}
IflaCan::RestartMs => {
params.restart_ms = Some(attr.get_payload_as::<u32>()?);
}
IflaCan::BerrCounter => {
params.berr_counter = Some(attr.get_payload_as::<CanBerrCounter>()?);
}
IflaCan::DataBitTiming => {
params.data_bit_timing = Some(attr.get_payload_as::<CanBitTiming>()?);
}
IflaCan::DataBitTimingConst => {
params.data_bit_timing_const =
Some(attr.get_payload_as::<CanBitTimingConst>()?);
}
IflaCan::Termination => {
params.termination = Some(attr.get_payload_as::<u16>()?);
}
_ => (),
}
}
}
}
Ok(params)
}
}
impl TryFrom<&InterfaceCanParams> for RtBuffer<Ifla, Buffer> {
type Error = NlError;
fn try_from(params: &InterfaceCanParams) -> Result<Self, Self::Error> {
let mut rtattrs: RtBuffer<Ifla, Buffer> = RtBuffer::new();
let mut data = Rtattr::new(None, IflaInfo::Data, Buffer::new())?;
if let Some(bt) = params.bit_timing {
data.add_nested_attribute(&Rtattr::new(None, IflaCan::BitTiming, bt)?)?;
}
if let Some(r) = params.restart_ms {
data.add_nested_attribute(&Rtattr::new(
None,
IflaCan::RestartMs,
&r.to_ne_bytes()[..],
)?)?;
}
if let Some(cm) = params.ctrl_mode {
data.add_nested_attribute(&Rtattr::new::<can_ctrlmode>(
None,
IflaCan::CtrlMode,
cm.into(),
)?)?;
}
if let Some(dbt) = params.data_bit_timing {
data.add_nested_attribute(&Rtattr::new(None, IflaCan::DataBitTiming, dbt)?)?;
}
if let Some(t) = params.termination {
data.add_nested_attribute(&Rtattr::new(None, IflaCan::Termination, t)?)?;
}
let mut link_info = Rtattr::new(None, Ifla::Linkinfo, Buffer::new())?;
link_info.add_nested_attribute(&Rtattr::new(None, IflaInfo::Kind, "can")?)?;
link_info.add_nested_attribute(&data)?;
rtattrs.push(link_info);
Ok(rtattrs)
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CanCtrlMode {
Loopback,
ListenOnly,
TripleSampling,
OneShot,
BerrReporting,
Fd,
PresumeAck,
NonIso,
CcLen8Dlc,
}
impl CanCtrlMode {
pub fn mask(&self) -> u32 {
1u32 << (*self as u32)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CanCtrlModes(can_ctrlmode);
impl CanCtrlModes {
pub fn new(mask: u32, flags: u32) -> Self {
Self(can_ctrlmode { mask, flags })
}
pub fn from_mode(mode: CanCtrlMode, on: bool) -> Self {
let mask = mode.mask();
let flags = if on { mask } else { 0 };
Self::new(mask, flags)
}
pub fn add(&mut self, mode: CanCtrlMode, on: bool) {
let mask = mode.mask();
self.0.mask |= mask;
if on {
self.0.flags |= mask;
}
}
#[inline]
pub fn clear(&mut self) {
self.0 = can_ctrlmode::default();
}
#[inline]
pub fn has_mode(&self, mode: CanCtrlMode) -> bool {
(mode.mask() & self.0.flags) != 0
}
}
impl From<can_ctrlmode> for CanCtrlModes {
fn from(mode: can_ctrlmode) -> Self {
Self(mode)
}
}
impl From<CanCtrlModes> for can_ctrlmode {
fn from(mode: CanCtrlModes) -> Self {
mode.0
}
}
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct CanInterface {
if_index: c_uint,
}
impl CanInterface {
pub fn open(ifname: &str) -> Result<Self, nix::Error> {
let if_index = if_nametoindex(ifname)?;
Ok(Self::open_iface(if_index))
}
pub fn open_iface(if_index: u32) -> Self {
let if_index = if_index as c_uint;
Self { if_index }
}
fn info_msg(&self, buf: RtBuffer<Ifla, Buffer>) -> Ifinfomsg {
Ifinfomsg::new(
RtAddrFamily::Unspecified,
Arphrd::Netrom,
self.if_index as c_int,
IffFlags::empty(),
IffFlags::empty(),
buf,
)
}
fn send_info_msg(msg_type: Rtm, info: Ifinfomsg, additional_flags: &[NlmF]) -> NlResult<()> {
let mut nl = Self::open_route_socket()?;
let hdr = Nlmsghdr::new(
None,
msg_type,
{
let mut flags = NlmFFlags::new(&[NlmF::Request, NlmF::Ack]);
for flag in additional_flags {
flags.set(flag);
}
flags
},
None,
None,
NlPayload::Payload(info),
);
Self::send_and_read_ack(&mut nl, hdr)
}
fn send_and_read_ack<T, P>(sock: &mut NlSocketHandle, msg: Nlmsghdr<T, P>) -> NlResult<()>
where
T: NlType + Debug,
P: ToBytes + Debug,
{
sock.send(msg)?;
if let Some(Nlmsghdr {
nl_payload: NlPayload::Ack(_),
..
}) = sock.recv()?
{
Ok(())
} else {
Err(NlError::NoAck)
}
}
fn open_route_socket<T, P>() -> Result<NlSocketHandle, NlError<T, P>> {
let pid = unistd::Pid::this().as_raw() as u32;
let sock = NlSocketHandle::connect(NlFamily::Route, Some(pid), &[])?;
Ok(sock)
}
fn query_details(&self) -> Result<Option<Nlmsghdr<Rtm, Ifinfomsg>>, NlInfoError> {
let mut sock = Self::open_route_socket()?;
let info = self.info_msg({
let mut buffer = RtBuffer::new();
buffer.push(Rtattr::new(None, Ifla::ExtMask, rt::EXT_FILTER_VF).unwrap());
buffer
});
let hdr = Nlmsghdr::new(
None,
Rtm::Getlink,
NlmFFlags::new(&[NlmF::Request]),
None,
None,
NlPayload::Payload(info),
);
sock.send(hdr)?;
sock.recv::<'_, Rtm, Ifinfomsg>()
}
pub fn bring_down(&self) -> NlResult<()> {
let info = Ifinfomsg::down(
RtAddrFamily::Unspecified,
Arphrd::Netrom,
self.if_index as c_int,
RtBuffer::new(),
);
Self::send_info_msg(Rtm::Newlink, info, &[])
}
pub fn bring_up(&self) -> NlResult<()> {
let info = Ifinfomsg::up(
RtAddrFamily::Unspecified,
Arphrd::Netrom,
self.if_index as c_int,
RtBuffer::new(),
);
Self::send_info_msg(Rtm::Newlink, info, &[])
}
pub fn create_vcan(name: &str, index: Option<u32>) -> NlResult<Self> {
Self::create(name, index, "vcan")
}
pub fn create<I>(name: &str, index: I, kind: &str) -> NlResult<Self>
where
I: Into<Option<u32>>,
{
if name.len() > libc::IFNAMSIZ {
return Err(NlError::Msg("Interface name too long".into()));
}
let index = index.into();
let info = Ifinfomsg::new(
RtAddrFamily::Unspecified,
Arphrd::Netrom,
index.unwrap_or(0) as c_int,
IffFlags::empty(),
IffFlags::empty(),
{
let mut buffer = RtBuffer::new();
buffer.push(Rtattr::new(None, Ifla::Ifname, name)?);
let mut linkinfo = Rtattr::new(None, Ifla::Linkinfo, Vec::<u8>::new())?;
linkinfo.add_nested_attribute(&Rtattr::new(None, IflaInfo::Kind, kind)?)?;
buffer.push(linkinfo);
buffer
},
);
Self::send_info_msg(Rtm::Newlink, info, &[NlmF::Create, NlmF::Excl])?;
if let Some(if_index) = index {
Ok(Self { if_index })
} else {
if let Ok(if_index) = if_nametoindex(name) {
Ok(Self { if_index })
} else {
Err(NlError::Msg(
"Interface must have been deleted between request and this if_nametoindex"
.into(),
))
}
}
}
pub fn delete(self) -> Result<(), (Self, NlError)> {
let info = self.info_msg(RtBuffer::new());
match Self::send_info_msg(Rtm::Dellink, info, &[]) {
Ok(()) => Ok(()),
Err(err) => Err((self, err)),
}
}
pub fn details(&self) -> Result<InterfaceDetails, NlInfoError> {
match self.query_details()? {
Some(msg_hdr) => {
let mut info = InterfaceDetails::new(self.if_index);
if let Ok(payload) = msg_hdr.get_payload() {
info.is_up = payload.ifi_flags.contains(&Iff::Up);
for attr in payload.rtattrs.iter() {
match attr.rta_type {
Ifla::Ifname => {
info.name = CStr::from_bytes_with_nul(attr.rta_payload.as_ref())
.map(|s| s.to_string_lossy().into_owned())
.ok();
}
Ifla::Mtu => {
info.mtu = attr
.get_payload_as::<u32>()
.ok()
.and_then(|mtu| Mtu::try_from(mtu).ok());
}
Ifla::Linkinfo => {
info.can = InterfaceCanParams::try_from(attr)?;
}
_ => (),
}
}
}
Ok(info)
}
None => Err(NlError::NoAck),
}
}
pub fn set_mtu(&self, mtu: Mtu) -> NlResult<()> {
let mtu = mtu as u32;
let info = self.info_msg({
let mut buffer = RtBuffer::new();
buffer.push(Rtattr::new(None, Ifla::Mtu, &mtu.to_ne_bytes()[..])?);
buffer
});
Self::send_info_msg(Rtm::Newlink, info, &[])
}
pub fn set_can_param<P>(&self, param_type: IflaCan, param: P) -> NlResult<()>
where
P: ToBytes + neli::Size,
{
let info = self.info_msg({
let mut data = Rtattr::new(None, IflaInfo::Data, Buffer::new())?;
data.add_nested_attribute(&Rtattr::new(None, param_type, param)?)?;
let mut link_info = Rtattr::new(None, Ifla::Linkinfo, Buffer::new())?;
link_info.add_nested_attribute(&Rtattr::new(None, IflaInfo::Kind, "can")?)?;
link_info.add_nested_attribute(&data)?;
let mut rtattrs = RtBuffer::new();
rtattrs.push(link_info);
rtattrs
});
Self::send_info_msg(Rtm::Newlink, info, &[])
}
pub fn set_can_params(&self, params: &InterfaceCanParams) -> NlResult<()> {
let info = self.info_msg(
RtBuffer::try_from(params)?,
);
Self::send_info_msg(Rtm::Newlink, info, &[])
}
pub fn can_param<P>(&self, param: IflaCan) -> Result<Option<P>, NlInfoError>
where
P: for<'a> FromBytes<'a> + Clone,
{
if let Some(hdr) = self.query_details()? {
if let Ok(payload) = hdr.get_payload() {
for top_attr in payload.rtattrs.iter() {
if top_attr.rta_type == Ifla::Linkinfo {
for info in top_attr.get_attr_handle::<IflaInfo>()?.get_attrs() {
if info.rta_type == IflaInfo::Data {
for attr in info.get_attr_handle::<IflaCan>()?.get_attrs() {
if attr.rta_type == param {
return Ok(Some(attr.get_payload_as::<P>()?));
}
}
}
}
}
}
}
Ok(None)
} else {
Err(NlError::NoAck)
}
}
pub fn bit_rate(&self) -> Result<Option<u32>, NlInfoError> {
Ok(self.bit_timing()?.map(|timing| timing.bitrate))
}
pub fn set_bitrate<P>(&self, bitrate: u32, sample_point: P) -> NlResult<()>
where
P: Into<Option<u32>>,
{
let sample_point: u32 = sample_point.into().unwrap_or(0);
debug_assert!(
0 < bitrate && bitrate <= 1000000,
"Bitrate must be within 1..=1000000, received {}.",
bitrate
);
debug_assert!(
sample_point < 1000,
"Sample point must be within 0..1000, received {}.",
sample_point
);
self.set_bit_timing(CanBitTiming {
bitrate,
sample_point,
..CanBitTiming::default()
})
}
pub fn bit_timing(&self) -> Result<Option<CanBitTiming>, NlInfoError> {
self.can_param::<CanBitTiming>(IflaCan::BitTiming)
}
pub fn set_bit_timing(&self, timing: CanBitTiming) -> NlResult<()> {
self.set_can_param(IflaCan::BitTiming, timing)
}
pub fn bit_timing_const(&self) -> Result<Option<CanBitTimingConst>, NlInfoError> {
self.can_param::<CanBitTimingConst>(IflaCan::BitTimingConst)
}
pub fn clock(&self) -> Result<Option<u32>, NlInfoError> {
Ok(self
.can_param::<CanClock>(IflaCan::Clock)?
.map(|clk| clk.freq))
}
pub fn state(&self) -> Result<Option<CanState>, NlInfoError> {
Ok(self
.can_param::<u32>(IflaCan::State)?
.and_then(|st| CanState::try_from(st).ok()))
}
#[deprecated(since = "3.2.0", note = "Use `set_ctrlmodes` instead")]
pub fn set_full_ctrlmode(&self, ctrlmode: can_ctrlmode) -> NlResult<()> {
self.set_can_param(IflaCan::CtrlMode, ctrlmode)
}
pub fn set_ctrlmodes<M>(&self, ctrlmode: M) -> NlResult<()>
where
M: Into<CanCtrlModes>,
{
let modes = ctrlmode.into();
let modes: can_ctrlmode = modes.into();
self.set_can_param(IflaCan::CtrlMode, modes)
}
pub fn set_ctrlmode(&self, mode: CanCtrlMode, on: bool) -> NlResult<()> {
self.set_ctrlmodes(CanCtrlModes::from_mode(mode, on))
}
pub fn restart_ms(&self) -> Result<Option<u32>, NlInfoError> {
self.can_param::<u32>(IflaCan::RestartMs)
}
pub fn set_restart_ms(&self, restart_ms: u32) -> NlResult<()> {
self.set_can_param(IflaCan::RestartMs, &restart_ms.to_ne_bytes()[..])
}
pub fn restart(&self) -> NlResult<()> {
let restart_data: u32 = 1;
self.set_can_param(IflaCan::Restart, &restart_data.to_ne_bytes()[..])
}
pub fn berr_counter(&self) -> Result<Option<CanBerrCounter>, NlInfoError> {
self.can_param::<CanBerrCounter>(IflaCan::BerrCounter)
}
pub fn data_bit_timing(&self) -> Result<Option<CanBitTiming>, NlInfoError> {
self.can_param::<CanBitTiming>(IflaCan::DataBitTiming)
}
pub fn set_data_bit_timing(&self, timing: CanBitTiming) -> NlResult<()> {
self.set_can_param(IflaCan::DataBitTiming, timing)
}
pub fn set_data_bitrate<P>(&self, bitrate: u32, sample_point: P) -> NlResult<()>
where
P: Into<Option<u32>>,
{
let sample_point: u32 = sample_point.into().unwrap_or(0);
self.set_data_bit_timing(CanBitTiming {
bitrate,
sample_point,
..CanBitTiming::default()
})
}
pub fn data_bit_timing_const(&self) -> Result<Option<CanBitTimingConst>, NlInfoError> {
self.can_param::<CanBitTimingConst>(IflaCan::DataBitTimingConst)
}
pub fn set_termination(&self, termination: u16) -> NlResult<()> {
self.set_can_param(IflaCan::Termination, termination)
}
pub fn termination(&self) -> Result<Option<u16>, NlInfoError> {
self.can_param::<u16>(IflaCan::Termination)
}
}
#[cfg(feature = "netlink_tests")]
#[cfg(test)]
pub mod tests {
use super::*;
use serial_test::serial;
use std::ops::Deref;
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct TemporaryInterface {
interface: CanInterface,
}
impl TemporaryInterface {
#[allow(unused)]
pub fn new(name: &str) -> NlResult<Self> {
Ok(Self {
interface: CanInterface::create_vcan(name, None)?,
})
}
}
impl Drop for TemporaryInterface {
fn drop(&mut self) {
assert!(CanInterface::open_iface(self.interface.if_index)
.delete()
.is_ok());
}
}
impl Deref for TemporaryInterface {
type Target = CanInterface;
fn deref(&self) -> &Self::Target {
&self.interface
}
}
#[test]
#[serial]
fn up_down() {
let interface = TemporaryInterface::new("up_down").unwrap();
assert!(interface.bring_up().is_ok());
assert!(interface.details().unwrap().is_up);
assert!(interface.bring_down().is_ok());
assert!(!interface.details().unwrap().is_up);
}
#[test]
#[serial]
fn details() {
let interface = TemporaryInterface::new("info").unwrap();
let details = interface.details().unwrap();
assert_eq!("info", details.name.unwrap());
assert!(details.mtu.is_some());
assert!(!details.is_up);
}
#[test]
#[serial]
fn mtu() {
let interface = TemporaryInterface::new("mtu").unwrap();
assert!(interface.set_mtu(Mtu::Fd).is_ok());
assert_eq!(Mtu::Fd, interface.details().unwrap().mtu.unwrap());
assert!(interface.set_mtu(Mtu::Standard).is_ok());
assert_eq!(Mtu::Standard, interface.details().unwrap().mtu.unwrap());
}
}