ftth-rtnl 0.1.0

RTnetlink components for `ftth` suite of Rust FTTH CPE software.
Documentation
#![allow(unreachable_patterns)]

use ftth_common::channel::{AsyncWorldClient, AsyncWorldServer};

use futures::TryStreamExt;

use std::fmt::{Debug, Display};

pub(crate) type Client = AsyncWorldClient<RtnlLinkRequest, RtnlLinkResponse>;
pub(crate) type Server = AsyncWorldServer<RtnlLinkRequest, RtnlLinkResponse>;

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MacAddr {
    pub inner: [u8; 6],
}

impl MacAddr {
    pub const fn new(inner: [u8; 6]) -> Self {
        Self {
            inner,
        }
    }
}

impl Default for MacAddr {
    fn default() -> Self {
        Self {
            inner: [0; 6],
        }
    }
}

impl Debug for MacAddr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&format!("MacAddr({})", self))
    }
}

impl Display for MacAddr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&format!(
            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
            self.inner[0],
            self.inner[1],
            self.inner[2],
            self.inner[3],
            self.inner[4],
            self.inner[5],
        ))
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interface {
    pub if_name: String,
    pub if_id: u32,
}

#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum RtnlLinkRequest {
    InterfaceList,
    InterfaceGet {
        if_id: u32,
    },
    InterfaceGetByName {
        if_name: String,
    },
    MacAddrGet {
        if_id: u32,
    },
    MacAddrSet {
        if_id: u32,
        mac_addr: MacAddr,
    },
}

#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum RtnlLinkResponse {
    Success,
    Failed,
    NotImplemented,
    NotFound,
    InterfaceList(Vec<Interface>),
    Interface(Interface),
    MacAddr(MacAddr),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RtnlLinkClient {
    client: Client,
}

impl RtnlLinkClient {
    pub(crate) fn new(client: Client) -> Self {
        Self {
            client,
        }
    }

    pub fn interface_get_by_name(&self, name: &str) -> std::io::Result<Interface> {
        let name = name.to_owned();
        let res = self.client.send_request(RtnlLinkRequest::InterfaceGetByName { if_name: name })?;
        match res {
            RtnlLinkResponse::Interface(interface) => {
                return Ok(interface);
            },
            _ => {},
        }
        Err(std::io::Error::other("Not found"))
    }

    pub fn mac_addr_get(&self, if_id: u32) -> std::io::Result<Option<MacAddr>> {
        let res = self.client.send_request(RtnlLinkRequest::MacAddrGet { if_id })?;
        match res {
            RtnlLinkResponse::MacAddr(addr) => {
                return Ok(Some(addr));
            },
            _ => {},
        }
        Ok(None)
    }

    pub fn interface_list(&self) -> std::io::Result<Vec<Interface>> {
        let res = self.client.send_request(RtnlLinkRequest::InterfaceList)?;
        match res {
            RtnlLinkResponse::InterfaceList(list) => {
                return Ok(list);
            },
            _ => {},
        }
        Err(std::io::Error::other("Unknown error"))
    }
}

pub(crate) async fn run_server(mut server: Server, mut handle: rtnetlink::LinkHandle) {
    'reqloop: while let Some((req, respond)) = server.accept().await {
        match req {
            RtnlLinkRequest::InterfaceGetByName { if_name } => {
                let response = handle.get().match_name(if_name.to_owned()).execute();
                futures::pin_mut!(response);
                while let Ok(Some(response)) = response.try_next().await {
                    let if_index = response.header.index;
                    if if_index == 0 {
                        continue;
                    }

                    respond(RtnlLinkResponse::Interface(Interface { if_id: if_index, if_name: if_name.to_owned() }));
                    continue 'reqloop;
                }
                respond(RtnlLinkResponse::NotFound);
            },
            RtnlLinkRequest::MacAddrGet { if_id } => {
                let if_index = if_id;
                if if_index == 0 {
                    respond(RtnlLinkResponse::NotFound);
                    continue 'reqloop;
                }
                let response = handle.get().match_index(if_index).execute();
                futures::pin_mut!(response);
                while let Ok(Some(response)) = response.try_next().await {
                    for link in response.attributes.iter() {
                        match link {
                            netlink_packet_route::link::LinkAttribute::Address(addr) => {
                                respond(RtnlLinkResponse::MacAddr(MacAddr::new(addr[0..6].try_into().unwrap_or([0; 6]))));
                                continue 'reqloop;
                            }
                            _ => {}
                        }
                    }
                }
                respond(RtnlLinkResponse::NotFound);
            },
            RtnlLinkRequest::InterfaceList => {
                let mut interfaces = Vec::new();
                let response = handle.get().execute();
                futures::pin_mut!(response);
                while let Ok(Some(response)) = response.try_next().await {
                    let if_index = response.header.index;
                    let mut if_name = None;
                    for link in response.attributes.iter() {
                        match link {
                            netlink_packet_route::link::LinkAttribute::IfName(name) => {
                                if_name = Some(name.clone());
                            }
                            _ => {}
                        }
                    }

                    if if_index == 0 || if_name.is_none() {
                        continue;
                    }

                    interfaces.push(Interface { if_id: if_index, if_name: if_name.unwrap() });
                }
                respond(RtnlLinkResponse::InterfaceList(interfaces));
            }
            _ => respond(RtnlLinkResponse::NotImplemented),
        }
    }
}