wgctrl-rs 0.1.0

High level bindings to the WireGuard embeddable C library
Documentation
extern crate chrono;
extern crate libc;
extern crate wgctrl_sys;

use key::Key;

use std::ffi::{CStr, CString};
use std::io;
use std::mem;
use std::net::{IpAddr, SocketAddr};
use std::ptr;
use std::str;

use self::chrono::TimeZone;

/// Represents an IP address a peer is allowed to have, in CIDR notation.
///
/// This may have unexpected semantics - refer to the
/// [WireGuard documentation](https://www.wireguard.com/#cryptokey-routing)
/// for more information on how routing is implemented.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AllowedIp {
    /// The IP address.
    pub address: IpAddr,
    /// The CIDR subnet mask.
    pub cidr: u8,
}

impl<'a> From<&'a wgctrl_sys::wg_allowedip> for AllowedIp {
    fn from(raw: &wgctrl_sys::wg_allowedip) -> AllowedIp {
        let addr = match i32::from(raw.family) {
            libc::AF_INET => IpAddr::V4(unsafe { raw.__bindgen_anon_1.ip4 }.s_addr.to_be().into()),
            libc::AF_INET6 => IpAddr::V6(unsafe { raw.__bindgen_anon_1.ip6 }.s6_addr.into()),
            _ => unreachable!(format!("Unsupported socket family {}!", raw.family)),
        };

        AllowedIp {
            address: addr,
            cidr: raw.cidr,
        }
    }
}

/// Represents a single peer's configuration (i.e. persistent attributes).
///
/// These are the attributes that don't change over time and are part of the configuration.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PeerConfig {
    /// The public key of the peer.
    pub public_key: Key,
    /// The preshared key available to both peers (`None` means no PSK is used).
    pub preshared_key: Option<Key>,
    /// The endpoint this peer listens for connections on (`None` means any).
    pub endpoint: Option<SocketAddr>,
    /// The interval for sending keepalive packets (`None` means disabled).
    pub persistent_keepalive_interval: Option<u16>,
    /// The IP addresses this peer is allowed to have.
    pub allowed_ips: Vec<AllowedIp>,
    __cant_construct_me: (),
}

/// Represents a single peer's current statistics (i.e. the data from the current session).
///
/// These are the attributes that will change over time; to update them,
/// re-read the information from the interface.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PeerStats {
    /// Time of the last handshake/rekey with this peer.
    pub last_handshake_time: Option<chrono::DateTime<chrono::Utc>>,
    /// Number of bytes received from this peer.
    pub rx_bytes: u64,
    /// Number of bytes transmitted to this peer.
    pub tx_bytes: u64,
    __cant_construct_me: (),
}

/// Represents the complete status of a peer.
///
/// This struct simply combines [`PeerInfo`](PeerInfo) and [`PeerStats`](PeerStats)
/// to represent all available information about a peer.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PeerInfo {
    pub config: PeerConfig,
    pub stats: PeerStats,
}

fn parse_allowed_ips(peer: &wgctrl_sys::wg_peer) -> Vec<AllowedIp> {
    let mut result = Vec::new();

    let mut current_ip: *mut wgctrl_sys::wg_allowedip = peer.first_allowedip;

    if current_ip.is_null() {
        return result;
    }

    loop {
        let ip = unsafe { &*current_ip };

        result.push(AllowedIp::from(ip));

        if current_ip == peer.last_allowedip {
            break;
        }
        current_ip = ip.next_allowedip;
    }

    result
}

unsafe fn parse_endpoint(endpoint: &wgctrl_sys::wg_peer__bindgen_ty_1) -> Option<SocketAddr> {
    match i32::from(endpoint.addr.sa_family) {
        libc::AF_INET => Some(SocketAddr::new(
            IpAddr::V4(endpoint.addr4.sin_addr.s_addr.into()),
            u16::from_be(endpoint.addr4.sin_port),
        )),
        libc::AF_INET6 => Some(SocketAddr::new(
            IpAddr::V6(endpoint.addr6.sin6_addr.s6_addr.into()),
            u16::from_be(endpoint.addr6.sin6_port),
        )),
        0 => None,
        _ => unreachable!(format!(
            "Unsupported socket family: {}!",
            endpoint.addr.sa_family
        )),
    }
}

impl<'a> From<&'a wgctrl_sys::wg_peer> for PeerInfo {
    fn from(raw: &wgctrl_sys::wg_peer) -> PeerInfo {
        use self::wgctrl_sys::wg_peer_flags as wgpf;

        PeerInfo {
            config: PeerConfig {
                public_key: Key::from_raw(raw.public_key),
                preshared_key: if (raw.flags & wgpf::WGPEER_HAS_PRESHARED_KEY).0 > 0 {
                    Some(Key::from_raw(raw.preshared_key))
                } else {
                    None
                },
                endpoint: unsafe { parse_endpoint(&raw.endpoint) },
                persistent_keepalive_interval: match raw.persistent_keepalive_interval {
                    0 => None,
                    x => Some(x),
                },
                allowed_ips: parse_allowed_ips(raw),
                __cant_construct_me: (),
            },
            stats: PeerStats {
                last_handshake_time: match (
                    raw.last_handshake_time.tv_sec,
                    raw.last_handshake_time.tv_nsec,
                ) {
                    (0, 0) => None,
                    (s, ns) => Some(chrono::Utc.timestamp(s, ns as u32)),
                },
                rx_bytes: raw.rx_bytes,
                tx_bytes: raw.tx_bytes,
                __cant_construct_me: (),
            },
        }
    }
}

/// Represents all available information about a WireGuard device (interface).
///
/// This struct contains the current configuration of the device
/// and the current configuration _and_ state of all of its peers.
/// The peer statistics are retrieved once at construction time,
/// and need to be updated manually by calling [`get_by_name`](DeviceInfo::get_by_name).
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DeviceInfo {
    /// The interface name of this device
    pub name: String,
    /// The public encryption key of this interface (if present)
    pub public_key: Option<Key>,
    /// The private encryption key of this interface (if present)
    pub private_key: Option<Key>,
    /// The [fwmark](https://www.linux.org/docs/man8/tc-fw.html) of this interface
    pub fwmark: Option<u32>,
    /// The port to listen for incoming connections on
    pub listen_port: Option<u16>,
    /// The list of all registered peers and their information
    pub peers: Vec<PeerInfo>,
    __cant_construct_me: (),
}

fn parse_peers(dev: &wgctrl_sys::wg_device) -> Vec<PeerInfo> {
    let mut result = Vec::new();

    let mut current_peer = dev.first_peer;

    if current_peer.is_null() {
        return result;
    }

    loop {
        let peer = unsafe { &*current_peer };

        result.push(PeerInfo::from(peer));

        if current_peer == dev.last_peer {
            break;
        }
        current_peer = peer.next_peer;
    }

    result
}

fn parse_device_name(name: [i8; 16]) -> String {
    let name: [u8; 16] = unsafe { mem::transmute(name) };
    let idx: usize = name
        .iter()
        .position(|x| *x == 0)
        .expect("Interface name too long?");
    unsafe { str::from_utf8_unchecked(&name[..idx]) }.to_owned()
}

impl<'a> From<&'a wgctrl_sys::wg_device> for DeviceInfo {
    fn from(raw: &wgctrl_sys::wg_device) -> DeviceInfo {
        use self::wgctrl_sys::wg_device_flags as wgdf;

        DeviceInfo {
            name: parse_device_name(raw.name),
            public_key: if (raw.flags & wgdf::WGDEVICE_HAS_PUBLIC_KEY).0 > 0 {
                Some(Key::from_raw(raw.public_key))
            } else {
                None
            },
            private_key: if (raw.flags & wgdf::WGDEVICE_HAS_PRIVATE_KEY).0 > 0 {
                Some(Key::from_raw(raw.private_key))
            } else {
                None
            },
            fwmark: match raw.fwmark {
                0 => None,
                x => Some(x),
            },
            listen_port: match raw.listen_port {
                0 => None,
                x => Some(x),
            },
            peers: parse_peers(&raw),
            __cant_construct_me: (),
        }
    }
}

impl DeviceInfo {
    /// Enumerates all WireGuard interfaces currently present in the system
    /// and returns their names.
    ///
    /// You can use [`get_by_name`](DeviceInfo::get_by_name) to retrieve more
    /// detailed information on each interface.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use wgctrl_rs::*;
    /// # fn main() -> Result<(), std::io::Error> {
    /// for dev in DeviceInfo::enumerate()? {
    ///     println!("{:#?}", DeviceInfo::get_by_name(&dev));
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub fn enumerate() -> Result<Vec<String>, io::Error> {
        let base = unsafe { wgctrl_sys::wg_list_device_names() };

        if base.is_null() {
            return Err(io::Error::last_os_error());
        }

        let mut current = base;
        let mut result = Vec::new();

        loop {
            let next_dev = unsafe { CStr::from_ptr(current).to_bytes() };

            let len = next_dev.len();

            if len == 0 {
                break;
            }

            current = unsafe { current.add(len + 1) };
            result.push(unsafe { str::from_utf8_unchecked(next_dev) }.to_owned());
        }

        unsafe { libc::free(base as *mut libc::c_void) };

        Ok(result)
    }

    /// Loads all available information on a given interface (by name) from the kernel.
    ///
    /// Actually doing this probably requires root privileges.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use wgctrl_rs::*;
    /// # fn main() -> Result<(), std::io::Error> {
    /// for dev in DeviceInfo::enumerate()? {
    ///     if let Ok(dev) = DeviceInfo::get_by_name(&dev) {
    ///          println!(
    ///             "Successfully loaded interface {}; public key: {:?}",
    ///             dev.name, dev.public_key.map(|k| k.to_base64())
    ///          )
    ///     }
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub fn get_by_name(name: &str) -> Result<Self, io::Error> {
        let mut device: *mut wgctrl_sys::wg_device = ptr::null_mut();

        let cs = CString::new(name)?;

        let result = unsafe {
            wgctrl_sys::wg_get_device(
                (&mut device) as *mut _ as *mut *mut wgctrl_sys::wg_device,
                cs.as_ptr(),
            )
        };

        let result = if result == 0 {
            Ok(DeviceInfo::from(unsafe { &*device }))
        } else {
            Err(io::Error::last_os_error())
        };

        unsafe { wgctrl_sys::wg_free_device(device) };

        result
    }
}