wireguard-uapi 3.0.1

Control WireGuard interfaces.
Documentation
#[cfg(target_os = "linux")]
use {
    std::net::{IpAddr, Ipv6Addr},
    std::time::Duration,
    wireguard_uapi::{get, set, DeviceInterface, RouteSocket, WgSocket},
};

#[cfg(target_os = "linux")]
fn get_random_ifname() -> String {
    format!("wgtest{}", rand::random::<u16>())
}

#[cfg(target_os = "linux")]
fn parse_device_key(buf: &[u8]) -> [u8; 32] {
    let mut key = [0u8; 32];
    key.copy_from_slice(buf);
    key
}

#[cfg(target_os = "linux")]
fn create_set_allowed_ips(allowed_ips: &[get::AllowedIp]) -> Vec<set::AllowedIp> {
    allowed_ips
        .iter()
        .map(|allowed_ip| set::AllowedIp {
            ipaddr: &allowed_ip.ipaddr,
            cidr_mask: Some(allowed_ip.cidr_mask),
        })
        .collect()
}

#[cfg(target_os = "linux")]
#[test]
fn simple() -> anyhow::Result<()> {
    let mut test_device = get::Device {
        ifindex: 0,
        ifname: get_random_ifname(),
        private_key: Some(parse_device_key(&base64::decode(
            "EHhtoXVXpnXz31cx8nrAxQfvaRqe1vf343GVSyEtqUU=",
        )?)),
        public_key: Some(parse_device_key(&base64::decode(
            "MhBzmIBrzw8b8iF2FH4ejh/7Vumn6Q/KoR0H5+o7mlY=",
        )?)),
        listen_port: 1234,
        fwmark: 0,
        peers: vec![
            get::Peer {
                public_key: parse_device_key(&base64::decode(
                    "DNeiCuVE2CuDy9QH3K3/egRK1rdn/oThlPtWNc4FfSw=",
                )?),
                preshared_key: [0u8; 32],
                endpoint: Some("[::1]:8080".parse()?),
                persistent_keepalive_interval: 0,
                last_handshake_time: Duration::new(0, 0),
                rx_bytes: 0,
                tx_bytes: 0,
                allowed_ips: vec![
                    get::AllowedIp {
                        family: libc::AF_INET as u16,
                        ipaddr: "10.24.24.1".parse()?,
                        cidr_mask: 32,
                    },
                    get::AllowedIp {
                        family: libc::AF_INET as u16,
                        ipaddr: "10.24.25.0".parse()?,
                        cidr_mask: 24,
                    },
                ],
                protocol_version: 1,
            },
            get::Peer {
                public_key: parse_device_key(&base64::decode(
                    "6KUaqULa+M6JI+b6DP3p0ZZZWyClN7ioMpYJp0kNFxQ=",
                )?),
                preshared_key: parse_device_key(&base64::decode(
                    "cMeE5GWUzUbvxbnBKco2MwAnW78nsk8vr04+KupVFkQ=",
                )?),
                endpoint: Some("127.0.0.1:12345".parse()?),
                persistent_keepalive_interval: 60,
                last_handshake_time: Duration::new(0, 0),
                rx_bytes: 0,
                tx_bytes: 0,
                allowed_ips: vec![get::AllowedIp {
                    family: libc::AF_INET6 as u16,
                    ipaddr: "::1".parse()?,
                    cidr_mask: 128,
                }],
                protocol_version: 1,
            },
        ],
    };

    let response_device = {
        let mut wg = WgSocket::connect()?;
        let mut route = RouteSocket::connect()?;

        println!("{}", test_device.ifname);
        route.add_device(&test_device.ifname)?;

        let set_device_args = set::Device::from_ifname(&test_device.ifname)
            .private_key(test_device.private_key.as_ref().unwrap())
            .listen_port(test_device.listen_port)
            .flags(vec![set::WgDeviceF::ReplacePeers])
            .peers(vec![
                set::Peer::from_public_key(&test_device.peers[0].public_key)
                    .endpoint(test_device.peers[0].endpoint.as_ref().unwrap())
                    .allowed_ips(create_set_allowed_ips(&test_device.peers[0].allowed_ips)),
                set::Peer::from_public_key(&test_device.peers[1].public_key)
                    .preshared_key(&test_device.peers[1].preshared_key)
                    .endpoint(test_device.peers[1].endpoint.as_ref().unwrap())
                    .persistent_keepalive_interval(
                        test_device.peers[1].persistent_keepalive_interval,
                    )
                    .allowed_ips(create_set_allowed_ips(&test_device.peers[1].allowed_ips)),
            ]);

        wg.set_device(set_device_args)?;
        let response_device = wg.get_device(DeviceInterface::from_name(&test_device.ifname))?;
        route.del_device(&test_device.ifname)?;

        response_device
    };

    // The ifindex can't be determined before response_device is set. So we'll just copy over the
    // newly generated index before testing that what we sent matches what was returned.
    test_device.ifindex = response_device.ifindex;

    assert_eq!(test_device, response_device);

    Ok(())
}

#[cfg(target_os = "linux")]
#[test]
fn set_ifname_has_proper_padding() -> anyhow::Result<()> {
    let ifname = get_random_ifname();
    let listen_port = rand::random::<u16>();

    let response_device = {
        let mut wg = WgSocket::connect()?;
        let mut route = RouteSocket::connect()?;

        route.add_device(&ifname)?;
        let set_device_args = set::Device::from_ifname(&ifname).listen_port(listen_port);
        wg.set_device(set_device_args)?;
        let response_device = wg.get_device(DeviceInterface::from_name(&ifname))?;
        route.del_device(&ifname)?;
        response_device
    };

    // If ifname wasn't properly padded, the listen_port won't be properly set. Check that it is
    // properly set as a rough measure that ifname was properly padded.
    assert_eq!(listen_port, response_device.listen_port);

    Ok(())
}

#[cfg(target_os = "linux")]
#[test]
fn large_peer() -> anyhow::Result<()> {
    let mut test_device = get::Device {
        ifindex: 6,
        ifname: get_random_ifname(),
        private_key: Some(parse_device_key(&base64::decode(
            "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=",
        )?)),
        public_key: Some(parse_device_key(&base64::decode(
            "HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=",
        )?)),
        listen_port: 51820,
        fwmark: 0,
        peers: vec![get::Peer {
            public_key: parse_device_key(&base64::decode(
                "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=",
            )?),
            preshared_key: [0u8; 32],
            endpoint: Some("192.95.5.67:1234".parse()?),
            persistent_keepalive_interval: 0,
            last_handshake_time: Duration::new(0, 0),
            rx_bytes: 0,
            tx_bytes: 0,
            allowed_ips: (1..=2u16.pow(12))
                .step_by(1)
                .map(|i| get::AllowedIp {
                    family: libc::AF_INET6 as u16,
                    ipaddr: IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, i / 256, i % 256)),
                    cidr_mask: 128,
                })
                .collect(),
            protocol_version: 1,
        }],
    };

    let response_device = {
        let mut wg = WgSocket::connect()?;
        let mut route = RouteSocket::connect()?;

        route.add_device(&test_device.ifname)?;

        let set_device_args = {
            let peer = set::Peer::from_public_key(&test_device.peers[0].public_key)
                .preshared_key(&test_device.peers[0].preshared_key)
                .endpoint(test_device.peers[0].endpoint.as_ref().unwrap())
                .persistent_keepalive_interval(test_device.peers[0].persistent_keepalive_interval)
                .allowed_ips(create_set_allowed_ips(&test_device.peers[0].allowed_ips));

            set::Device::from_ifname(&test_device.ifname)
                .private_key(test_device.private_key.as_ref().unwrap())
                .listen_port(test_device.listen_port)
                .flags(vec![set::WgDeviceF::ReplacePeers])
                .peers(vec![peer])
        };

        wg.set_device(set_device_args)?;
        let response_device = wg.get_device(DeviceInterface::from_name(&test_device.ifname))?;
        route.del_device(&test_device.ifname)?;

        response_device
    };

    // The ifindex can't be determined before response_device is set. So we'll just copy over the
    // newly generated index before testing that what we sent matches what was returned.
    test_device.ifindex = response_device.ifindex;

    assert_eq!(test_device, response_device);

    Ok(())
}

#[cfg(target_os = "linux")]
#[test]
fn peer_update_only() -> anyhow::Result<()> {
    let test_device = get::Device {
        ifindex: 6,
        ifname: get_random_ifname(),
        private_key: Some(parse_device_key(&base64::decode(
            "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=",
        )?)),
        public_key: Some(parse_device_key(&base64::decode(
            "HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=",
        )?)),
        listen_port: 51820,
        fwmark: 0,
        peers: vec![],
    };

    let response_device = {
        let mut wg = WgSocket::connect()?;
        let mut route = RouteSocket::connect()?;

        route.add_device(&test_device.ifname)?;

        let pubkey = parse_device_key(&base64::decode(
            "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=",
        )?);
        let set_device_args = set::Device::from_ifname(&test_device.ifname)
            .private_key(test_device.private_key.as_ref().unwrap())
            .listen_port(test_device.listen_port)
            .peers(vec![
                set::Peer::from_public_key(&pubkey).flags(vec![set::WgPeerF::UpdateOnly])
            ]);

        wg.set_device(set_device_args)?;
        let response_device = wg.get_device(DeviceInterface::from_name(&test_device.ifname))?;
        route.del_device(&test_device.ifname)?;

        response_device
    };

    assert_eq!(response_device.peers, vec![]);

    Ok(())
}