l2tp 0.2.0

High-level L2TP interface for Linux kernel
Documentation
use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TunnelId(pub u32);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SessionId(pub u32);

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cookie(Vec<u8>);

impl Cookie {
    pub fn none() -> Self {
        Self(Vec::new())
    }

    pub fn try_from_bytes(b: Vec<u8>) -> crate::Result<Self> {
        match b.len() {
            0 | 4 | 8 => Ok(Self(b)),
            n => Err(crate::Error::InvalidCookieLength(n)),
        }
    }

    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfName(String);

impl IfName {
    pub fn new(s: impl Into<String>) -> crate::Result<Self> {
        let s = s.into();
        let b = s.as_bytes();

        if b.is_empty() {
            return Err(crate::Error::InvalidIfName(s));
        }
        if b.len() > (libc::IFNAMSIZ - 1) {
            return Err(crate::Error::InvalidIfName(s));
        }
        if b.contains(&0) {
            return Err(crate::Error::InvalidIfName(s));
        }
        if b.contains(&b'/') {
            return Err(crate::Error::InvalidIfName(s));
        }

        Ok(Self(s))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for IfName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UdpEndpoint {
    V4(std::net::SocketAddrV4),
    V6(std::net::SocketAddrV6),
}

impl UdpEndpoint {
    pub fn ip_version(&self) -> u8 {
        match self {
            Self::V4(_) => 4,
            Self::V6(_) => 6,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpEndpoint {
    V4(std::net::Ipv4Addr),
    V6(std::net::Ipv6Addr),
}

impl IpEndpoint {
    pub fn ip_version(&self) -> u8 {
        match self {
            Self::V4(_) => 4,
            Self::V6(_) => 6,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Encapsulation {
    Udp {
        local: UdpEndpoint,
        remote: UdpEndpoint,
        udp_csum: bool,
        udp_zero_csum6_tx: bool,
        udp_zero_csum6_rx: bool,
    },
    Ip {
        local: IpEndpoint,
        remote: IpEndpoint,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PseudowireType {
    Eth,
    EthVlan,
    Ppp,
    PppAc,
    Ip,
    None,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum L2SpecType {
    #[default]
    None,
    Default,
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};

    #[test]
    fn cookie_accepts_valid_lengths() {
        let c0 = Cookie::try_from_bytes(vec![]).unwrap();
        let c4 = Cookie::try_from_bytes(vec![1, 2, 3, 4]).unwrap();
        let c8 = Cookie::try_from_bytes(vec![1, 2, 3, 4, 5, 6, 7, 8]).unwrap();
        assert_eq!(c0.as_bytes(), &[]);
        assert_eq!(c4.as_bytes(), &[1, 2, 3, 4]);
        assert_eq!(c8.as_bytes(), &[1, 2, 3, 4, 5, 6, 7, 8]);
    }

    #[test]
    fn cookie_rejects_invalid_length() {
        let err = Cookie::try_from_bytes(vec![1, 2, 3]).unwrap_err();
        match err {
            crate::Error::InvalidCookieLength(3) => {}
            other => panic!("unexpected error: {other:?}"),
        }
    }

    #[test]
    fn ifname_validates_constraints() {
        let ok = IfName::new("l2tp0").unwrap();
        assert_eq!(ok.as_str(), "l2tp0");
        assert_eq!(ok.to_string(), "l2tp0");

        assert!(matches!(
            IfName::new(""),
            Err(crate::Error::InvalidIfName(_))
        ));
        assert!(matches!(
            IfName::new("abc/def"),
            Err(crate::Error::InvalidIfName(_))
        ));
        assert!(matches!(
            IfName::new("ab\0cd"),
            Err(crate::Error::InvalidIfName(_))
        ));
        assert!(matches!(
            IfName::new("0123456789abcdef"),
            Err(crate::Error::InvalidIfName(_))
        ));
    }

    #[test]
    fn endpoint_ip_versions_are_reported() {
        let udp4 = UdpEndpoint::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1701));
        let udp6 = UdpEndpoint::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 1701, 0, 0));
        let ip4 = IpEndpoint::V4(Ipv4Addr::LOCALHOST);
        let ip6 = IpEndpoint::V6(Ipv6Addr::LOCALHOST);

        assert_eq!(udp4.ip_version(), 4);
        assert_eq!(udp6.ip_version(), 6);
        assert_eq!(ip4.ip_version(), 4);
        assert_eq!(ip6.ip_version(), 6);
    }

    #[test]
    fn l2spec_default_is_none() {
        assert_eq!(L2SpecType::default(), L2SpecType::None);
    }
}