use crate::Result;
use neli::{
FromBytes, FromBytesWithInput, Size, ToBytes,
attr::Attribute,
consts::{
nl::{NlType, NlmF},
rtnl::{Arphrd, Iff, Ifla, IflaInfo, RtAddrFamily, Rtm},
socket::NlFamily,
},
err::RouterError,
nl::{NlPayload, Nlmsghdr, NlmsghdrBuilder},
rtnl::{Ifinfomsg, IfinfomsgBuilder, Rtattr, RtattrBuilder},
socket::synchronous::NlSocketHandle,
types::{Buffer, RtBuffer},
utils::Groups,
};
use nix::{self, net::if_::if_nametoindex};
use rt::{IflaCan, IflaCanCtrlModeExt};
use std::{ffi::CStr, fmt::Debug, io, os::raw::c_uint};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
mod rt;
use rt::can_ctrlmode;
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NlError {
#[error("netlink error: {}", io::Error::from_raw_os_error(*errno))]
Netlink {
errno: i32,
},
#[error("no netlink ack received")]
NoAck,
#[error("unexpected netlink ack received")]
UnexpectedAck,
#[error("netlink reply with bad sequence number or port id (seq {seq}, pid {pid})")]
BadSeqOrPid {
seq: u32,
pid: u32,
},
#[error("netlink channel closed")]
ClosedChannel,
#[error("netlink: {0}")]
Msg(String),
}
impl NlError {
pub fn errno(&self) -> Option<i32> {
match *self {
Self::Netlink { errno } => Some(errno),
_ => None,
}
}
pub fn io_kind(&self) -> Option<io::ErrorKind> {
self.errno().map(|e| io::Error::from_raw_os_error(e).kind())
}
}
impl<T, P> From<RouterError<T, P>> for NlError
where
T: NlType,
P: Debug,
{
fn from(e: RouterError<T, P>) -> Self {
use RouterError::*;
match e {
Nlmsgerr(err) => Self::Netlink {
errno: -*err.error(),
},
NoAck => Self::NoAck,
UnexpectedAck => Self::UnexpectedAck,
ClosedChannel => Self::ClosedChannel,
BadSeqOrPid(msg) => Self::BadSeqOrPid {
seq: *msg.nl_seq(),
pid: *msg.nl_pid(),
},
other => Self::Msg(other.to_string()),
}
}
}
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)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct InterfaceDetails {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub name: Option<String>,
pub index: c_uint,
pub is_up: bool,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
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)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Mtu {
Standard = 16,
Fd = 72,
}
impl TryFrom<u32> for Mtu {
type Error = io::Error;
fn try_from(val: u32) -> std::result::Result<Self, Self::Error> {
match val {
16 => Ok(Mtu::Standard),
72 => Ok(Mtu::Fd),
_ => Err(io::Error::from(io::ErrorKind::InvalidData)),
}
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CanState {
ErrorActive,
ErrorWarning,
ErrorPassive,
BusOff,
Stopped,
Sleeping,
}
impl TryFrom<u32> for CanState {
type Error = io::Error;
fn try_from(val: u32) -> std::result::Result<Self, Self::Error> {
match val {
libc::CAN_STATE_ERROR_ACTIVE => Ok(Self::ErrorActive),
libc::CAN_STATE_ERROR_WARNING => Ok(Self::ErrorWarning),
libc::CAN_STATE_ERROR_PASSIVE => Ok(Self::ErrorPassive),
libc::CAN_STATE_BUS_OFF => Ok(Self::BusOff),
libc::CAN_STATE_STOPPED => Ok(Self::Stopped),
libc::CAN_STATE_SLEEPING => Ok(Self::Sleeping),
_ => Err(io::Error::from(io::ErrorKind::InvalidData)),
}
}
}
#[allow(missing_copy_implementations)]
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct InterfaceCanParams {
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub bit_timing: Option<CanBitTiming>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub bit_timing_const: Option<CanBitTimingConst>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub clock: Option<CanClock>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub state: Option<CanState>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub restart_ms: Option<u32>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub berr_counter: Option<CanBerrCounter>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub ctrl_mode: Option<CanCtrlModes>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub data_bit_timing: Option<CanBitTiming>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub data_bit_timing_const: Option<CanBitTimingConst>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub termination: Option<u16>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub ctrl_mode_supported: Option<u32>,
}
impl InterfaceCanParams {
pub(crate) fn from_link_info(link_info: &Rtattr<Ifla, Buffer>) -> Result<Self> {
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() {
let attr_type = IflaCan::from(u16::from(attr.rta_type()) & !rt::NLA_F_NESTED);
match &attr_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>()?);
}
IflaCan::CtrlModeExt => {
params.ctrl_mode_supported = Self::supported_from_nest(attr)?;
}
_ => (),
}
}
}
}
Ok(params)
}
fn supported_from_nest(attr: &Rtattr<IflaCan, Buffer>) -> Result<Option<u32>> {
for inner in attr.get_attr_handle::<IflaCanCtrlModeExt>()?.get_attrs() {
if *inner.rta_type() == IflaCanCtrlModeExt::Supported {
return Ok(Some(inner.get_payload_as::<u32>()?));
}
}
Ok(None)
}
pub(crate) fn to_rtbuffer(&self) -> Result<RtBuffer<Ifla, Buffer>> {
let mut rtattrs: RtBuffer<Ifla, Buffer> = RtBuffer::new();
let mut data = RtattrBuilder::default()
.rta_type(IflaInfo::Data)
.rta_payload(Buffer::new())
.build()?;
if let Some(bt) = self.bit_timing {
data = data.nest(
&RtattrBuilder::default()
.rta_type(IflaCan::BitTiming)
.rta_payload(bt)
.build()?,
)?;
}
if let Some(r) = self.restart_ms {
data = data.nest(
&RtattrBuilder::default()
.rta_type(IflaCan::RestartMs)
.rta_payload(&r.to_ne_bytes()[..])
.build()?,
)?;
}
if let Some(cm) = self.ctrl_mode {
data = data.nest(
&RtattrBuilder::<_, can_ctrlmode>::default()
.rta_type(IflaCan::CtrlMode)
.rta_payload(cm.into())
.build()?,
)?;
}
if let Some(dbt) = self.data_bit_timing {
data = data.nest(
&RtattrBuilder::default()
.rta_type(IflaCan::DataBitTiming)
.rta_payload(dbt)
.build()?,
)?;
}
if let Some(t) = self.termination {
data = data.nest(
&RtattrBuilder::default()
.rta_type(IflaCan::Termination)
.rta_payload(t)
.build()?,
)?;
}
let mut link_info = RtattrBuilder::default()
.rta_type(Ifla::Linkinfo)
.rta_payload(Buffer::new())
.build()?;
link_info = link_info.nest(
&RtattrBuilder::default()
.rta_type(IflaInfo::Kind)
.rta_payload("can")
.build()?,
)?;
link_info = link_info.nest(&data)?;
rtattrs.push(link_info);
Ok(rtattrs)
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
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)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
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,
}
fn requested_index(index: impl Into<Option<u32>>) -> Option<u32> {
index.into().filter(|index| *index != 0)
}
impl CanInterface {
pub fn open(ifname: &str) -> Result<Self> {
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 {
IfinfomsgBuilder::default()
.ifi_family(RtAddrFamily::Unspecified)
.ifi_type(Arphrd::Netrom)
.ifi_index(self.if_index as i32)
.rtattrs(buf)
.build()
.unwrap()
}
fn send_info_msg(msg_type: Rtm, info: Ifinfomsg, additional_flags: NlmF) -> Result<()> {
let mut nl = Self::open_route_socket()?;
let hdr = NlmsghdrBuilder::default()
.nl_type(msg_type)
.nl_flags(NlmF::REQUEST | NlmF::ACK | additional_flags)
.nl_payload(NlPayload::Payload(info))
.build()
.unwrap();
Self::send_and_read_ack(&mut nl, &hdr)
}
fn send_and_read_ack<T, P>(sock: &mut NlSocketHandle, msg: &Nlmsghdr<T, P>) -> Result<()>
where
T: NlType + Debug,
P: ToBytes + Debug + Size + FromBytesWithInput<Input = usize>,
{
sock.send(msg)?;
if sock
.recv::<T, P>()?
.0
.next()
.transpose()?
.is_some_and(|msg| matches!(msg.nl_payload(), NlPayload::Ack(_)))
{
Ok(())
} else {
Err(NlError::NoAck.into())
}
}
fn open_route_socket() -> Result<NlSocketHandle> {
let sock = NlSocketHandle::connect(NlFamily::Route, None, Groups::empty())?;
Ok(sock)
}
fn query_details(&self) -> Result<Option<Nlmsghdr<Rtm, Ifinfomsg>>> {
let sock = Self::open_route_socket()?;
let info = self.info_msg({
let mut buffer = RtBuffer::new();
buffer.push(
RtattrBuilder::default()
.rta_type(Ifla::ExtMask)
.rta_payload(libc::RTEXT_FILTER_VF as c_uint)
.build()
.unwrap(),
);
buffer
});
let hdr = NlmsghdrBuilder::default()
.nl_type(Rtm::Getlink)
.nl_flags(NlmF::REQUEST)
.nl_payload(NlPayload::Payload(info))
.build()
.unwrap();
sock.send(&hdr)?;
let mut iter = sock.recv::<Rtm, Ifinfomsg>()?.0;
let Some(msg) = iter.next().transpose()? else {
return Ok(None);
};
if let NlPayload::Err(err) = msg.nl_payload() {
let errno = -*err.error();
if errno != 0 {
return Err(NlError::Netlink { errno }.into());
}
}
Ok(Some(msg))
}
pub fn bring_down(&self) -> Result<()> {
let info = IfinfomsgBuilder::default()
.down()
.ifi_family(RtAddrFamily::Unspecified)
.ifi_type(Arphrd::Netrom)
.ifi_index(self.if_index as i32)
.rtattrs(RtBuffer::new())
.build()
.unwrap();
Self::send_info_msg(Rtm::Newlink, info, NlmF::empty())
}
pub fn bring_up(&self) -> Result<()> {
let info = IfinfomsgBuilder::default()
.up()
.ifi_family(RtAddrFamily::Unspecified)
.ifi_type(Arphrd::Netrom)
.ifi_index(self.if_index as i32)
.build()
.unwrap();
Self::send_info_msg(Rtm::Newlink, info, NlmF::empty())
}
pub fn create_vcan(name: &str, index: Option<u32>) -> Result<Self> {
Self::create(name, index, "vcan")
}
pub fn create<I>(name: &str, index: I, kind: &str) -> Result<Self>
where
I: Into<Option<u32>>,
{
if name.len() >= libc::IFNAMSIZ {
return Err(NlError::Msg("Interface name too long".into()).into());
}
let index = requested_index(index);
let info = IfinfomsgBuilder::default()
.ifi_family(RtAddrFamily::Unspecified)
.ifi_type(Arphrd::Netrom)
.ifi_index(index.unwrap_or(0) as i32)
.rtattrs({
let mut buffer = RtBuffer::new();
buffer.push(
RtattrBuilder::default()
.rta_type(Ifla::Ifname)
.rta_payload(name)
.build()?,
);
let linkinfo = RtattrBuilder::default()
.rta_type(Ifla::Linkinfo)
.rta_payload(Vec::<u8>::new())
.build()?
.nest(
&RtattrBuilder::default()
.rta_type(IflaInfo::Kind)
.rta_payload(kind)
.build()?,
)?;
buffer.push(linkinfo);
buffer
})
.build()
.unwrap();
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(),
)
.into())
}
}
}
pub fn delete(self) -> std::result::Result<(), (Self, crate::Error)> {
let info = self.info_msg(RtBuffer::new());
match Self::send_info_msg(Rtm::Dellink, info, NlmF::empty()) {
Ok(()) => Ok(()),
Err(err) => Err((self, err)),
}
}
pub fn details(&self) -> Result<InterfaceDetails> {
match self.query_details()? {
Some(msg_hdr) => {
let mut info = InterfaceDetails::new(self.if_index);
if let Some(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_until_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::from_link_info(attr)?;
}
_ => (),
}
}
}
Ok(info)
}
None => Err(NlError::NoAck.into()),
}
}
pub fn set_mtu(&self, mtu: Mtu) -> Result<()> {
let mtu = mtu as u32;
let info = self.info_msg({
let mut buffer = RtBuffer::new();
buffer.push(
RtattrBuilder::default()
.rta_type(Ifla::Mtu)
.rta_payload(&mtu.to_ne_bytes()[..])
.build()?,
);
buffer
});
Self::send_info_msg(Rtm::Newlink, info, NlmF::empty())
}
pub fn can_param_bytes(&self, id: u16) -> Result<Option<Vec<u8>>> {
let Some(hdr) = self.query_details()? else {
return Err(NlError::NoAck.into());
};
let Some(payload) = hdr.get_payload() else {
return Ok(None);
};
for top_attr in payload.rtattrs().iter() {
if *top_attr.rta_type() != Ifla::Linkinfo {
continue;
}
for info in top_attr.get_attr_handle::<IflaInfo>()?.get_attrs() {
if *info.rta_type() != IflaInfo::Data {
continue;
}
for attr in info.get_attr_handle::<IflaCan>()?.get_attrs() {
if u16::from(attr.rta_type()) & !rt::NLA_F_NESTED == id {
return Ok(Some(attr.rta_payload().as_ref().to_vec()));
}
}
}
}
Ok(None)
}
pub fn set_can_param_bytes(&self, id: u16, data: &[u8]) -> Result<()> {
self.set_can_param(IflaCan::from(id), data)
}
pub(crate) fn set_can_param<P>(&self, param_type: IflaCan, param: P) -> Result<()>
where
P: ToBytes + Size,
{
let info = self.info_msg({
let data = RtattrBuilder::default()
.rta_type(IflaInfo::Data)
.rta_payload(Buffer::new())
.build()?
.nest(
&RtattrBuilder::default()
.rta_type(param_type)
.rta_payload(param)
.build()?,
)?;
let link_info = RtattrBuilder::default()
.rta_type(Ifla::Linkinfo)
.rta_payload(Buffer::new())
.build()?
.nest(
&RtattrBuilder::default()
.rta_type(IflaInfo::Kind)
.rta_payload("can")
.build()?,
)?
.nest(&data)?;
let mut rtattrs = RtBuffer::new();
rtattrs.push(link_info);
rtattrs
});
Self::send_info_msg(Rtm::Newlink, info, NlmF::empty())
}
pub fn set_can_params(&self, params: &InterfaceCanParams) -> Result<()> {
let info = self.info_msg(params.to_rtbuffer()?);
Self::send_info_msg(Rtm::Newlink, info, NlmF::empty())
}
pub fn can_params(&self) -> Result<InterfaceCanParams> {
let Some(hdr) = self.query_details()? else {
return Err(NlError::NoAck.into());
};
let Some(payload) = hdr.get_payload() else {
return Ok(InterfaceCanParams::default());
};
for attr in payload.rtattrs().iter() {
if *attr.rta_type() == Ifla::Linkinfo {
return InterfaceCanParams::from_link_info(attr);
}
}
Ok(InterfaceCanParams::default())
}
pub(crate) fn can_param<P>(&self, param: IflaCan) -> Result<Option<P>>
where
P: FromBytes + Clone,
{
if let Some(hdr) = self.query_details()? {
if let Some(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.into())
}
}
pub fn bit_rate(&self) -> Result<Option<u32>> {
Ok(self.bit_timing()?.map(|timing| timing.bitrate))
}
pub fn set_bitrate<P>(&self, bitrate: u32, sample_point: P) -> Result<()>
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>> {
self.can_param::<CanBitTiming>(IflaCan::BitTiming)
}
pub fn set_bit_timing(&self, timing: CanBitTiming) -> Result<()> {
self.set_can_param(IflaCan::BitTiming, timing)
}
pub fn bit_timing_const(&self) -> Result<Option<CanBitTimingConst>> {
self.can_param::<CanBitTimingConst>(IflaCan::BitTimingConst)
}
pub fn clock(&self) -> Result<Option<u32>> {
Ok(self
.can_param::<CanClock>(IflaCan::Clock)?
.map(|clk| clk.freq))
}
pub fn state(&self) -> Result<Option<CanState>> {
Ok(self
.can_param::<u32>(IflaCan::State)?
.and_then(|st| CanState::try_from(st).ok()))
}
pub fn set_ctrlmodes<M>(&self, ctrlmode: M) -> Result<()>
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) -> Result<()> {
self.set_ctrlmodes(CanCtrlModes::from_mode(mode, on))
}
pub fn ctrlmodes(&self) -> Result<Option<CanCtrlModes>> {
Ok(self
.can_param::<can_ctrlmode>(IflaCan::CtrlMode)?
.map(CanCtrlModes))
}
pub fn restart_ms(&self) -> Result<Option<u32>> {
self.can_param::<u32>(IflaCan::RestartMs)
}
pub fn set_restart_ms(&self, restart_ms: u32) -> Result<()> {
self.set_can_param(IflaCan::RestartMs, &restart_ms.to_ne_bytes()[..])
}
pub fn restart(&self) -> Result<()> {
let restart_data: u32 = 1;
self.set_can_param(IflaCan::Restart, &restart_data.to_ne_bytes()[..])
}
pub fn berr_counter(&self) -> Result<Option<CanBerrCounter>> {
self.can_param::<CanBerrCounter>(IflaCan::BerrCounter)
}
pub fn data_bit_timing(&self) -> Result<Option<CanBitTiming>> {
self.can_param::<CanBitTiming>(IflaCan::DataBitTiming)
}
pub fn set_data_bit_timing(&self, timing: CanBitTiming) -> Result<()> {
self.set_can_param(IflaCan::DataBitTiming, timing)
}
pub fn set_data_bitrate<P>(&self, bitrate: u32, sample_point: P) -> Result<()>
where
P: Into<Option<u32>>,
{
let sample_point: u32 = sample_point.into().unwrap_or(0);
debug_assert!(
0 < bitrate && bitrate <= 8000000,
"Data bitrate must be within 1..=8000000, received {}.",
bitrate
);
debug_assert!(
sample_point < 1000,
"Sample point must be within 0..1000, received {}.",
sample_point
);
self.set_data_bit_timing(CanBitTiming {
bitrate,
sample_point,
..CanBitTiming::default()
})
}
pub fn data_bit_timing_const(&self) -> Result<Option<CanBitTimingConst>> {
self.can_param::<CanBitTimingConst>(IflaCan::DataBitTimingConst)
}
pub fn set_termination(&self, termination: u16) -> Result<()> {
self.set_can_param(IflaCan::Termination, termination)
}
pub fn supported_ctrlmodes(&self) -> Result<Option<u32>> {
Ok(self.can_params()?.ctrl_mode_supported)
}
pub fn termination(&self) -> Result<Option<u16>> {
self.can_param::<u16>(IflaCan::Termination)
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn router_error_summary() {
type RtErr = RouterError<Rtm, Ifinfomsg>;
assert_eq!(NlError::from(RtErr::NoAck), NlError::NoAck);
assert_eq!(NlError::from(RtErr::UnexpectedAck), NlError::UnexpectedAck);
assert_eq!(NlError::from(RtErr::ClosedChannel), NlError::ClosedChannel);
assert!(matches!(
NlError::from(RtErr::new("malformed attribute")),
NlError::Msg(_)
));
assert!(matches!(
NlError::from(RtErr::Io(io::ErrorKind::PermissionDenied)),
NlError::Msg(_)
));
}
#[cfg(feature = "vcan_tests")]
#[test]
fn can_params_agrees_with_details() {
let iface = CanInterface::open("vcan0").expect("vcan0 must exist");
let params = iface.can_params().expect("can_params");
let details = iface.details().expect("details");
assert_eq!(format!("{params:?}"), format!("{:?}", details.can));
}
#[test]
fn query_on_a_missing_interface_reports_enodev() {
let iface = CanInterface::open_iface(999_999);
let results: [(&str, Result<()>); 4] = [
("details", iface.details().map(|_| ())),
("can_params", iface.can_params().map(|_| ())),
("bit_timing", iface.bit_timing().map(|_| ())),
("state", iface.state().map(|_| ())),
];
for (name, res) in results {
match res {
Err(crate::Error::Nl(NlError::Netlink { errno })) => {
assert_eq!(errno, libc::ENODEV, "{name}");
}
other => panic!("{name}: expected ENODEV, got {other:?}"),
}
}
}
#[test]
fn index_zero_is_unspecified() {
assert_eq!(requested_index(None), None);
assert_eq!(requested_index(0), None);
assert_eq!(requested_index(Some(0)), None);
assert_eq!(requested_index(1), Some(1));
assert_eq!(requested_index(Some(42)), Some(42));
}
}
#[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) -> Result<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());
}
}