netdig 0.0.5

Utilities for analyzing and aggregating CIDR blocks
Documentation
use ipnet::Ipv4Net;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, fmt::Debug, net::Ipv4Addr};
#[cfg(feature = "tracing")]
use tracing::instrument;

// TODO: Could this be simplified with a macro?
const RFC6890: [Ipv4Net; 4] = [
    match Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 8) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
    match Ipv4Net::new(Ipv4Addr::new(127, 0, 0, 0), 8) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
    match Ipv4Net::new(Ipv4Addr::new(192, 0, 0, 0), 24) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
    match Ipv4Net::new(Ipv4Addr::new(255, 255, 255, 255), 32) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
];
const RFC1918: [Ipv4Net; 3] = [
    match Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 0), 8) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
    match Ipv4Net::new(Ipv4Addr::new(172, 16, 0, 0), 12) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
    match Ipv4Net::new(Ipv4Addr::new(192, 168, 0, 0), 16) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
];
const RFC6598: [Ipv4Net; 1] =
    [match Ipv4Net::new(Ipv4Addr::new(169, 254, 0, 0), 16) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    }];
const RFC3927: [Ipv4Net; 1] =
    [match Ipv4Net::new(Ipv4Addr::new(100, 64, 0, 0), 10) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    }];
const RFC7526: [Ipv4Net; 1] =
    [match Ipv4Net::new(Ipv4Addr::new(192, 88, 99, 0), 24) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    }];

const RFC2544: [Ipv4Net; 1] =
    [match Ipv4Net::new(Ipv4Addr::new(198, 18, 0, 0), 15) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    }];

const RFC5737: [Ipv4Net; 2] = [
    match Ipv4Net::new(Ipv4Addr::new(198, 51, 100, 0), 24) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
    match Ipv4Net::new(Ipv4Addr::new(203, 0, 113, 0), 24) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
];

const RFC5771: [Ipv4Net; 2] = [
    match Ipv4Net::new(Ipv4Addr::new(224, 18, 0, 0), 4) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
    match Ipv4Net::new(Ipv4Addr::new(233, 252, 0, 0), 24) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    },
];

const RFC3232: [Ipv4Net; 1] =
    [match Ipv4Net::new(Ipv4Addr::new(240, 0, 0, 0), 4) {
        Ok(cidr) => cidr,
        Err(_) => panic!("invalid cidr"),
    }];

pub(crate) fn all() -> Vec<Box<dyn Warn>> {
    vec![Box::new(Private), Box::new(LargeMask)]
}

#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Warning(pub String);

impl From<String> for Warning {
    fn from(value: String) -> Self {
        Self(value)
    }
}

impl Borrow<str> for Warning {
    fn borrow(&self) -> &str {
        &self.0
    }
}

pub(crate) trait Warn: Debug {
    fn check(&self, cidr: Ipv4Net) -> Option<Warning>;
}

#[derive(Clone, Debug, Default)]
pub(crate) struct Private;

impl Warn for Private {
    #[cfg_attr(feature = "tracing", instrument(ret))]
    fn check(&self, cidr: Ipv4Net) -> Option<Warning> {
        for block in [
            &RFC1918[..],
            &RFC2544[..],
            &RFC3232[..],
            &RFC3927[..],
            &RFC5737[..],
            &RFC5771[..],
            &RFC6598[..],
            &RFC6890[..],
            &RFC7526[..],
        ]
        .concat()
        {
            if block.contains(&cidr) {
                return Some(format!("RESERVED ADDRESS SPACE: {cidr} is in the {block} private address space").into());
            }
        }

        None
    }
}

#[derive(Clone, Debug, Default)]
pub(crate) struct LargeMask;

impl Warn for LargeMask {
    #[cfg_attr(feature = "tracing", instrument(ret))]
    fn check(&self, cidr: Ipv4Net) -> Option<Warning> {
        if cidr.prefix_len() < 24 {
            return Some(
                format!(
                    "LARGE MASK: {cidr} contains {} hosts",
                    cidr.hosts().count()
                )
                .into(),
            );
        }

        None
    }
}