ftth-rtnl 0.2.1

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

use std::io::{self, ErrorKind};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

use futures::TryStreamExt;

use ftth_common::channel::{AsyncWorldClient, AsyncWorldServer};
use netlink_packet_route::{
    AddressFamily,
    address::{AddressAttribute, AddressMessage},
};

pub(crate) type Client = AsyncWorldClient<RtnlAddressRequest, RtnlAddressResponse>;
pub(crate) type Server = AsyncWorldServer<RtnlAddressRequest, RtnlAddressResponse>;

#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum RtnlAddressRequest {
    Ipv4AddrsGet { if_id: u32 },
    Ipv6AddrsGet { if_id: u32 },
    Ipv4AddrSet { prefix: crate::Ipv4Net, if_id: u32 },
    Ipv6AddrSet { prefix: crate::Ipv6Net, if_id: u32 },
    Ipv4AddrDel { prefix: crate::Ipv4Net, if_id: u32 },
    Ipv6AddrDel { prefix: crate::Ipv6Net, if_id: u32 },
}

#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum RtnlAddressResponse {
    Success,
    Failed,
    NotImplemented,
    NotFound,
    Ipv4Addrs(Vec<Ipv4Addr>),
    Ipv6Addrs(Vec<Ipv6Addr>),
}

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

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

    pub fn ipv4_addrs_get(&self, if_id: Option<u32>) -> std::io::Result<Vec<Ipv4Addr>> {
        let res = self.client.send_request(RtnlAddressRequest::Ipv4AddrsGet {
            if_id: if_id.unwrap_or(0),
        })?;
        match res {
            RtnlAddressResponse::Ipv4Addrs(addrs) => {
                return Ok(addrs);
            }
            _ => {}
        }
        Err(std::io::Error::other("Failed to get IPv4 addresses"))
    }

    pub fn ipv6_addrs_get(&self, if_id: Option<u32>) -> std::io::Result<Vec<Ipv6Addr>> {
        let res = self.client.send_request(RtnlAddressRequest::Ipv6AddrsGet {
            if_id: if_id.unwrap_or(0),
        })?;
        match res {
            RtnlAddressResponse::Ipv6Addrs(addrs) => {
                return Ok(addrs);
            }
            _ => {}
        }
        Err(std::io::Error::other("Failed to get IPv6 addresses"))
    }

    pub fn ipv4_addr_set(&self, if_id: u32, prefix: crate::Ipv4Net) -> io::Result<()> {
        let res = self
            .client
            .send_request(RtnlAddressRequest::Ipv4AddrSet { prefix, if_id })?;
        handle_basic_response("IPv4 address set", res, false)
    }

    pub fn ipv6_addr_set(&self, if_id: u32, prefix: crate::Ipv6Net) -> io::Result<()> {
        let res = self
            .client
            .send_request(RtnlAddressRequest::Ipv6AddrSet { prefix, if_id })?;
        handle_basic_response("IPv6 address set", res, false)
    }

    pub fn ipv4_addr_del(&self, if_id: u32, prefix: crate::Ipv4Net) -> io::Result<()> {
        let res = self
            .client
            .send_request(RtnlAddressRequest::Ipv4AddrDel { prefix, if_id })?;
        handle_basic_response("IPv4 address delete", res, true)
    }

    pub fn ipv6_addr_del(&self, if_id: u32, prefix: crate::Ipv6Net) -> io::Result<()> {
        let res = self
            .client
            .send_request(RtnlAddressRequest::Ipv6AddrDel { prefix, if_id })?;
        handle_basic_response("IPv6 address delete", res, true)
    }
}

fn build_ipv4_address_message(prefix: &crate::Ipv4Net, if_id: u32) -> AddressMessage {
    let mut message = AddressMessage::default();
    message.header.family = AddressFamily::Inet;
    message.header.index = if_id;
    message.header.prefix_len = prefix.prefix_len();

    let addr = prefix.addr();
    if !addr.is_multicast() {
        message
            .attributes
            .push(AddressAttribute::Address(addr.into()));
        message
            .attributes
            .push(AddressAttribute::Local(addr.into()));

        let broadcast = if prefix.prefix_len() == 32 {
            addr
        } else {
            let host_bits = 0xffff_ffff_u32 >> u32::from(prefix.prefix_len());
            let ip_addr = u32::from(addr);
            Ipv4Addr::from(ip_addr | host_bits)
        };
        message
            .attributes
            .push(AddressAttribute::Broadcast(broadcast));
    }

    message
}

fn build_ipv6_address_message(prefix: &crate::Ipv6Net, if_id: u32) -> AddressMessage {
    let mut message = AddressMessage::default();
    message.header.family = AddressFamily::Inet6;
    message.header.index = if_id;
    message.header.prefix_len = prefix.prefix_len();

    let addr = prefix.addr();
    if addr.is_multicast() {
        message.attributes.push(AddressAttribute::Multicast(addr));
    } else {
        message
            .attributes
            .push(AddressAttribute::Address(addr.into()));
        message
            .attributes
            .push(AddressAttribute::Local(addr.into()));
    }

    message
}

fn handle_basic_response(
    operation: &str,
    response: RtnlAddressResponse,
    is_delete: bool,
) -> io::Result<()> {
    match response {
        RtnlAddressResponse::Success => Ok(()),
        RtnlAddressResponse::Failed => {
            Err(io::Error::other(format!("{} request failed", operation)))
        }
        RtnlAddressResponse::NotImplemented => Err(io::Error::new(
            ErrorKind::Unsupported,
            format!("{} request is not implemented", operation),
        )),
        RtnlAddressResponse::NotFound => Err(io::Error::new(
            ErrorKind::NotFound,
            if is_delete {
                format!("{} target not found", operation)
            } else {
                format!("{} not found", operation)
            },
        )),
        unexpected => Err(io::Error::other(format!(
            "{} returned unexpected response: {:?}",
            operation, unexpected
        ))),
    }
}

pub(crate) async fn run_server(mut server: Server, handle: rtnetlink::AddressHandle) {
    while let Some((req, respond)) = server.accept().await {
        match req {
            RtnlAddressRequest::Ipv4AddrsGet { if_id } => {
                let if_index = if_id;
                let mut addrs = Vec::new();
                let mut req = handle.get();
                if if_index != 0 {
                    req = req.set_link_index_filter(if_index);
                }
                let response = req.execute();

                futures::pin_mut!(response);
                while let Ok(Some(response)) = response.try_next().await {
                    if response.header.family != netlink_packet_route::AddressFamily::Inet {
                        continue;
                    }
                    for addr in response.attributes.iter() {
                        if let netlink_packet_route::address::AddressAttribute::Address(
                            std::net::IpAddr::V4(addr),
                        ) = addr
                        {
                            addrs.push(*addr);
                        }
                    }
                }
                respond(RtnlAddressResponse::Ipv4Addrs(addrs));
            }
            RtnlAddressRequest::Ipv6AddrsGet { if_id } => {
                let if_index = if_id;
                let mut addrs = Vec::new();
                let mut req = handle.get();
                if if_index != 0 {
                    req = req.set_link_index_filter(if_index);
                }
                let response = req.execute();

                futures::pin_mut!(response);
                while let Ok(Some(response)) = response.try_next().await {
                    if response.header.family != netlink_packet_route::AddressFamily::Inet6 {
                        continue;
                    }
                    for addr in response.attributes.iter() {
                        if let netlink_packet_route::address::AddressAttribute::Address(
                            std::net::IpAddr::V6(addr),
                        ) = addr
                        {
                            addrs.push(*addr);
                        }
                    }
                }
                respond(RtnlAddressResponse::Ipv6Addrs(addrs));
            }
            RtnlAddressRequest::Ipv4AddrSet { prefix, if_id } => {
                if if_id == 0 {
                    respond(RtnlAddressResponse::Failed);
                    continue;
                }

                let addr = prefix.addr();
                let prefix_len = prefix.prefix_len();
                let result = handle
                    .add(if_id, IpAddr::V4(addr), prefix_len)
                    .execute()
                    .await;

                match result {
                    Ok(()) => respond(RtnlAddressResponse::Success),
                    Err(rtnetlink::Error::NetlinkError(err_msg))
                        if err_msg.to_io().kind() == ErrorKind::AlreadyExists =>
                    {
                        respond(RtnlAddressResponse::Success);
                    }
                    Err(err) => {
                        log::warn!(
                            "Failed to add IPv4 address {}/{} on ifindex {}: {}",
                            addr,
                            prefix_len,
                            if_id,
                            err,
                        );
                        respond(RtnlAddressResponse::Failed);
                    }
                }
            }
            RtnlAddressRequest::Ipv6AddrSet { prefix, if_id } => {
                if if_id == 0 {
                    respond(RtnlAddressResponse::Failed);
                    continue;
                }

                let addr = prefix.addr();
                let prefix_len = prefix.prefix_len();
                let result = handle
                    .add(if_id, IpAddr::V6(addr), prefix_len)
                    .execute()
                    .await;

                match result {
                    Ok(()) => respond(RtnlAddressResponse::Success),
                    Err(rtnetlink::Error::NetlinkError(err_msg))
                        if err_msg.to_io().kind() == ErrorKind::AlreadyExists =>
                    {
                        respond(RtnlAddressResponse::Success);
                    }
                    Err(err) => {
                        log::warn!(
                            "Failed to add IPv6 address {}/{} on ifindex {}: {}",
                            addr,
                            prefix_len,
                            if_id,
                            err,
                        );
                        respond(RtnlAddressResponse::Failed);
                    }
                }
            }
            RtnlAddressRequest::Ipv4AddrDel { prefix, if_id } => {
                if if_id == 0 {
                    respond(RtnlAddressResponse::Failed);
                    continue;
                }

                let addr = prefix.addr();
                let prefix_len = prefix.prefix_len();
                let message = build_ipv4_address_message(&prefix, if_id);

                let result = handle.del(message).execute().await;

                match result {
                    Ok(()) => respond(RtnlAddressResponse::Success),
                    Err(rtnetlink::Error::NetlinkError(err_msg)) => {
                        let io_err = err_msg.to_io();
                        if matches!(
                            io_err.kind(),
                            ErrorKind::AddrNotAvailable | ErrorKind::NotFound
                        ) {
                            respond(RtnlAddressResponse::NotFound);
                        } else {
                            log::warn!(
                                "Failed to delete IPv4 address {}/{} on ifindex {}: {}",
                                addr,
                                prefix_len,
                                if_id,
                                err_msg,
                            );
                            respond(RtnlAddressResponse::Failed);
                        }
                    }
                    Err(err) => {
                        log::warn!(
                            "Failed to delete IPv4 address {}/{} on ifindex {}: {}",
                            addr,
                            prefix_len,
                            if_id,
                            err,
                        );
                        respond(RtnlAddressResponse::Failed);
                    }
                }
            }
            RtnlAddressRequest::Ipv6AddrDel { prefix, if_id } => {
                if if_id == 0 {
                    respond(RtnlAddressResponse::Failed);
                    continue;
                }

                let addr = prefix.addr();
                let prefix_len = prefix.prefix_len();
                let message = build_ipv6_address_message(&prefix, if_id);

                let result = handle.del(message).execute().await;

                match result {
                    Ok(()) => respond(RtnlAddressResponse::Success),
                    Err(rtnetlink::Error::NetlinkError(err_msg)) => {
                        let io_err = err_msg.to_io();
                        if matches!(
                            io_err.kind(),
                            ErrorKind::AddrNotAvailable | ErrorKind::NotFound
                        ) {
                            respond(RtnlAddressResponse::NotFound);
                        } else {
                            log::warn!(
                                "Failed to delete IPv6 address {}/{} on ifindex {}: {}",
                                addr,
                                prefix_len,
                                if_id,
                                err_msg,
                            );
                            respond(RtnlAddressResponse::Failed);
                        }
                    }
                    Err(err) => {
                        log::warn!(
                            "Failed to delete IPv6 address {}/{} on ifindex {}: {}",
                            addr,
                            prefix_len,
                            if_id,
                            err,
                        );
                        respond(RtnlAddressResponse::Failed);
                    }
                }
            }
            _ => respond(RtnlAddressResponse::NotImplemented),
        }
    }
}