l2tp 0.2.0

High-level L2TP interface for Linux kernel
Documentation
use l2tp::{
    Cookie, Encapsulation, IfName, IpEndpoint, L2SpecType, L2tpHandle, PseudowireType,
    SessionConfig, SessionId, SessionModify, TunnelConfig, TunnelId, TunnelModify, TunnelSocket,
    UdpEndpoint,
};
use std::net::{Ipv4Addr, SocketAddrV4};
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

static ID_COUNTER: AtomicU32 = AtomicU32::new(0);

fn runtime() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap_or_else(|e| panic!("failed to build tokio runtime: {e}"))
}

fn next_id_base() -> u32 {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_nanos() as u64;
    let pid = std::process::id() as u64;
    let seq = ID_COUNTER.fetch_add(1, Ordering::Relaxed) as u64;
    let mixed = now ^ (pid << 16) ^ seq;
    1_000_000_000u32.wrapping_add((mixed as u32) % 500_000_000)
}

fn is_eexist(err: &l2tp::Error) -> bool {
    match err {
        l2tp::Error::KernelError { code, .. } => *code == libc::EEXIST,
        _ => false,
    }
}

#[test]
#[ignore = "requires CAP_NET_ADMIN/root (SO_BINDTODEVICE)"]
fn privileged_bind_to_device_on_loopback() {
    let local = UdpEndpoint::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0));
    let remote = UdpEndpoint::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999));
    let device = IfName::new("lo").unwrap_or_else(|e| panic!("invalid ifname: {e}"));

    let socket = TunnelSocket::udp(&local, &remote, None)
        .unwrap_or_else(|e| panic!("failed to create UDP tunnel socket: {e}"));
    socket
        .bind_to_device(&device)
        .unwrap_or_else(|e| panic!("SO_BINDTODEVICE failed: {e}"));
}

#[test]
#[ignore = "requires CAP_NET_ADMIN/root and kernel l2tp generic-netlink support"]
fn privileged_unmanaged_tunnel_lifecycle_over_genl() {
    runtime().block_on(async {
        let handle = L2tpHandle::new()
            .await
            .unwrap_or_else(|e| panic!("L2tpHandle::new failed: {e}"));
        let mut selected_ids = None;
        let mut tunnel_opt = None;
        for _ in 0..16 {
            let base = next_id_base();
            let tunnel_id = TunnelId(base);
            let peer_tunnel_id = TunnelId(base.wrapping_add(1));

            let config = TunnelConfig::new(
                tunnel_id,
                peer_tunnel_id,
                Encapsulation::Udp {
                    local: UdpEndpoint::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 17011)),
                    remote: UdpEndpoint::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 17012)),
                    udp_csum: false,
                    udp_zero_csum6_tx: false,
                    udp_zero_csum6_rx: false,
                },
            )
            .unwrap_or_else(|e| panic!("TunnelConfig::new failed: {e}"));

            match handle.create_unmanaged_tunnel(config).await {
                Ok(mut tunnel) => {
                    tunnel.set_auto_delete(false);
                    selected_ids = Some((tunnel_id, peer_tunnel_id));
                    tunnel_opt = Some(tunnel);
                    break;
                }
                Err(e) if is_eexist(&e) => continue,
                Err(e) => panic!("create_unmanaged_tunnel failed: {e}"),
            }
        }

        let (tunnel_id, peer_tunnel_id) = selected_ids
            .unwrap_or_else(|| panic!("could not allocate unique tunnel id after retries"));
        let mut tunnel = tunnel_opt.unwrap_or_else(|| panic!("missing tunnel after creation"));
        tunnel.set_auto_delete(false);

        let info = tunnel
            .get()
            .await
            .unwrap_or_else(|e| panic!("tunnel get failed: {e}"));
        assert_eq!(info.tunnel_id, tunnel_id);
        assert_eq!(info.peer_tunnel_id, peer_tunnel_id);
        assert_eq!(info.proto_version, 3);

        tunnel
            .modify(TunnelModify {
                udp_csum: Some(true),
            })
            .await
            .unwrap_or_else(|e| panic!("tunnel modify failed: {e}"));

        handle
            .delete_tunnel(tunnel_id)
            .await
            .unwrap_or_else(|e| panic!("delete_tunnel failed: {e}"));
    });
}

#[test]
#[ignore = "requires CAP_NET_ADMIN/root and kernel l2tp_eth support"]
fn privileged_session_lifecycle_over_genl() {
    runtime().block_on(async {
        let handle = L2tpHandle::new()
            .await
            .unwrap_or_else(|e| panic!("L2tpHandle::new failed: {e}"));
        let mut selected_ids = None;
        let mut tunnel_opt = None;
        for _ in 0..16 {
            let base = next_id_base();
            let tunnel_id = TunnelId(base);
            let peer_tunnel_id = TunnelId(base.wrapping_add(1));
            let session_id = SessionId(base.wrapping_add(2));
            let peer_session_id = SessionId(base.wrapping_add(3));

            let tunnel_config = TunnelConfig::new(
                tunnel_id,
                peer_tunnel_id,
                Encapsulation::Ip {
                    local: IpEndpoint::V4(Ipv4Addr::new(127, 0, 0, 1)),
                    remote: IpEndpoint::V4(Ipv4Addr::new(127, 0, 0, 1)),
                },
            )
            .unwrap_or_else(|e| panic!("TunnelConfig::new failed: {e}"));

            match handle.create_unmanaged_tunnel(tunnel_config).await {
                Ok(mut tunnel) => {
                    tunnel.set_auto_delete(false);
                    selected_ids = Some((tunnel_id, peer_tunnel_id, session_id, peer_session_id));
                    tunnel_opt = Some(tunnel);
                    break;
                }
                Err(e) if is_eexist(&e) => continue,
                Err(e) => panic!("create_unmanaged_tunnel failed: {e}"),
            }
        }

        let (tunnel_id, _peer_tunnel_id, session_id, peer_session_id) = selected_ids
            .unwrap_or_else(|| {
                panic!("could not allocate unique tunnel/session ids after retries")
            });
        let mut tunnel = tunnel_opt.unwrap_or_else(|| panic!("missing tunnel after creation"));
        tunnel.set_auto_delete(false);

        let session_config = SessionConfig {
            tunnel_id,
            session_id,
            peer_session_id,
            pseudowire_type: PseudowireType::Eth,
            l2spec_type: L2SpecType::None,
            cookie: Cookie::none(),
            peer_cookie: Cookie::none(),
            recv_seq: false,
            send_seq: false,
            lns_mode: false,
            recv_timeout_ms: Some(2000),
            ifname: None,
        };

        let mut session = handle
            .create_session(session_config)
            .await
            .unwrap_or_else(|e| panic!("create_session failed: {e}"));
        session.set_auto_delete(false);

        let info = session
            .get()
            .await
            .unwrap_or_else(|e| panic!("session get failed: {e}"));
        assert_eq!(info.tunnel_id, tunnel_id);
        assert_eq!(info.session_id, session_id);
        assert_eq!(info.peer_session_id, peer_session_id);
        assert_eq!(info.pseudowire_type, PseudowireType::Eth);

        session
            .modify(SessionModify {
                recv_seq: Some(true),
                send_seq: Some(true),
                lns_mode: Some(false),
                recv_timeout_ms: Some(1500),
            })
            .await
            .unwrap_or_else(|e| panic!("session modify failed: {e}"));

        handle
            .delete_session(tunnel_id, session_id)
            .await
            .unwrap_or_else(|e| panic!("delete_session failed: {e}"));

        handle
            .delete_tunnel(tunnel_id)
            .await
            .unwrap_or_else(|e| panic!("delete_tunnel failed: {e}"));
    });
}