Skip to content
This repository has been archived by the owner on Oct 26, 2022. It is now read-only.

Wireguard packet support #191

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"netlink-packet-audit",
"netlink-packet-audit/fuzz",
"netlink-packet-sock-diag",
"netlink-packet-wireguard",
"netlink-proto",
"ethtool",
"genetlink",
Expand All @@ -26,6 +27,7 @@ default-members = [
"netlink-packet-route",
"netlink-packet-audit",
"netlink-packet-sock-diag",
"netlink-packet-wireguard",
"netlink-proto",
"ethtool",
"genetlink",
Expand Down
27 changes: 27 additions & 0 deletions netlink-packet-wireguard/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "netlink-packet-wireguard"
version = "0.1.0"
authors = ["Leo <leo881003@gmail.com>"]
edition = "2018"
homepage = "https://github.com/little-dude/netlink"
repository = "https://github.com/little-dude/netlink"
keywords = ["wireguard", "netlink", "linux"]
license = "MIT"
readme = "../README.md"
description = "Wireguard generic netlink packet definitions"

[dependencies]
anyhow = "1.0.42"
byteorder = "1.4.3"
libc = "0.2.98"
netlink-packet-generic = "0.1.0"
netlink-packet-utils = "0.4.1"

[dev-dependencies]
base64 = "0.13.0"
env_logger = "0.9.0"
futures = "0.3.16"
netlink-packet-core = "0.2.4"
netlink-proto = "0.7.0"
genetlink = "0.1.0"
tokio = { version = "1.9.0", features = ["macros", "rt-multi-thread"] }
105 changes: 105 additions & 0 deletions netlink-packet-wireguard/examples/get_wireguard_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use futures::StreamExt;
use genetlink::new_connection;
use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST};
use netlink_packet_generic::GenlMessage;
use netlink_packet_wireguard::{
nlas::{WgAllowedIpAttrs, WgDeviceAttrs, WgPeerAttrs},
Wireguard,
WireguardCmd,
};
use std::env::args;

#[tokio::main]
async fn main() {
env_logger::init();

let argv: Vec<String> = args().collect();
if argv.len() < 2 {
eprintln!("Usage: get_wireguard_info <ifname>");
return;
}

let (connection, mut handle, _) = new_connection().unwrap();
tokio::spawn(connection);

let genlmsg: GenlMessage<Wireguard> = GenlMessage::from_payload(Wireguard {
cmd: WireguardCmd::GetDevice,
nlas: vec![WgDeviceAttrs::IfName(argv[1].clone())],
});
let mut nlmsg = NetlinkMessage::from(genlmsg);
nlmsg.header.flags = NLM_F_REQUEST | NLM_F_DUMP;

let mut res = handle.request(nlmsg).await.unwrap();

while let Some(result) = res.next().await {
let rx_packet = result.unwrap();
match rx_packet.payload {
NetlinkPayload::InnerMessage(genlmsg) => {
print_wg_payload(genlmsg.payload);
}
NetlinkPayload::Error(e) => {
eprintln!("Error: {:?}", e.to_io());
}
_ => (),
};
}
}

fn print_wg_payload(wg: Wireguard) {
for nla in &wg.nlas {
match nla {
WgDeviceAttrs::IfIndex(v) => println!("IfIndex: {}", v),
WgDeviceAttrs::IfName(v) => println!("IfName: {}", v),
WgDeviceAttrs::PrivateKey(_) => println!("PrivateKey: (hidden)"),
WgDeviceAttrs::PublicKey(v) => println!("PublicKey: {}", base64::encode(v)),
WgDeviceAttrs::ListenPort(v) => println!("ListenPort: {}", v),
WgDeviceAttrs::Fwmark(v) => println!("Fwmark: {}", v),
WgDeviceAttrs::Peers(nlas) => {
for peer in nlas {
println!("Peer: ");
print_wg_peer(peer);
}
}
_ => (),
}
}
}

fn print_wg_peer(nlas: &[WgPeerAttrs]) {
for nla in nlas {
match nla {
WgPeerAttrs::PublicKey(v) => println!(" PublicKey: {}", base64::encode(v)),
WgPeerAttrs::PresharedKey(_) => println!(" PresharedKey: (hidden)"),
WgPeerAttrs::Endpoint(v) => println!(" Endpoint: {}", v),
WgPeerAttrs::PersistentKeepalive(v) => println!(" PersistentKeepalive: {}", v),
WgPeerAttrs::LastHandshake(v) => println!(" LastHandshake: {:?}", v),
WgPeerAttrs::RxBytes(v) => println!(" RxBytes: {}", v),
WgPeerAttrs::TxBytes(v) => println!(" TxBytes: {}", v),
WgPeerAttrs::AllowedIps(nlas) => {
for ip in nlas {
print_wg_allowedip(ip);
}
}
_ => (),
}
}
}

fn print_wg_allowedip(nlas: &[WgAllowedIpAttrs]) -> Option<()> {
let ipaddr = nlas.iter().find_map(|nla| {
if let WgAllowedIpAttrs::IpAddr(ipaddr) = nla {
Some(*ipaddr)
} else {
None
}
})?;
let cidr = nlas.iter().find_map(|nla| {
if let WgAllowedIpAttrs::Cidr(cidr) = nla {
Some(*cidr)
} else {
None
}
})?;
println!(" AllowedIp: {}/{}", ipaddr, cidr);
Some(())
}
37 changes: 37 additions & 0 deletions netlink-packet-wireguard/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pub const WG_KEY_LEN: usize = 32;

pub const WG_CMD_GET_DEVICE: u8 = 0;
pub const WG_CMD_SET_DEVICE: u8 = 1;

pub const WGDEVICE_F_REPLACE_PEERS: u32 = 1 << 0;

pub const WGDEVICE_A_UNSPEC: u16 = 0;
pub const WGDEVICE_A_IFINDEX: u16 = 1;
pub const WGDEVICE_A_IFNAME: u16 = 2;
pub const WGDEVICE_A_PRIVATE_KEY: u16 = 3;
pub const WGDEVICE_A_PUBLIC_KEY: u16 = 4;
pub const WGDEVICE_A_FLAGS: u16 = 5;
pub const WGDEVICE_A_LISTEN_PORT: u16 = 6;
pub const WGDEVICE_A_FWMARK: u16 = 7;
pub const WGDEVICE_A_PEERS: u16 = 8;

pub const WGPEER_F_REMOVE_ME: u32 = 1 << 0;
pub const WGPEER_F_REPLACE_ALLOWEDIPS: u32 = 1 << 1;
pub const WGPEER_F_UPDATE_ONLY: u32 = 1 << 2;

pub const WGPEER_A_UNSPEC: u16 = 0;
pub const WGPEER_A_PUBLIC_KEY: u16 = 1;
pub const WGPEER_A_PRESHARED_KEY: u16 = 2;
pub const WGPEER_A_FLAGS: u16 = 3;
pub const WGPEER_A_ENDPOINT: u16 = 4;
pub const WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: u16 = 5;
pub const WGPEER_A_LAST_HANDSHAKE_TIME: u16 = 6;
pub const WGPEER_A_RX_BYTES: u16 = 7;
pub const WGPEER_A_TX_BYTES: u16 = 8;
pub const WGPEER_A_ALLOWEDIPS: u16 = 9;
pub const WGPEER_A_PROTOCOL_VERSION: u16 = 10;

pub const WGALLOWEDIP_A_UNSPEC: u16 = 0;
pub const WGALLOWEDIP_A_FAMILY: u16 = 1;
pub const WGALLOWEDIP_A_IPADDR: u16 = 2;
pub const WGALLOWEDIP_A_CIDR_MASK: u16 = 3;
94 changes: 94 additions & 0 deletions netlink-packet-wireguard/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use crate::constants::*;
use anyhow::Context;
use netlink_packet_generic::{GenlFamily, GenlHeader};
use netlink_packet_utils::{nla::NlasIterator, traits::*, DecodeError};
use nlas::WgDeviceAttrs;
use std::convert::{TryFrom, TryInto};

pub mod constants;
pub mod nlas;
mod raw;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WireguardCmd {
GetDevice,
SetDevice,
}

impl From<WireguardCmd> for u8 {
fn from(cmd: WireguardCmd) -> Self {
use WireguardCmd::*;
match cmd {
GetDevice => WG_CMD_GET_DEVICE,
SetDevice => WG_CMD_SET_DEVICE,
}
}
}

impl TryFrom<u8> for WireguardCmd {
type Error = DecodeError;

fn try_from(value: u8) -> Result<Self, Self::Error> {
use WireguardCmd::*;
Ok(match value {
WG_CMD_GET_DEVICE => GetDevice,
WG_CMD_SET_DEVICE => SetDevice,
cmd => {
return Err(DecodeError::from(format!(
"Unknown wireguard command: {}",
cmd
)))
}
})
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Wireguard {
pub cmd: WireguardCmd,
pub nlas: Vec<nlas::WgDeviceAttrs>,
}

impl GenlFamily for Wireguard {
fn family_name() -> &'static str {
"wireguard"
}

fn version(&self) -> u8 {
1
}

fn command(&self) -> u8 {
self.cmd.into()
}
}

impl Emitable for Wireguard {
fn emit(&self, buffer: &mut [u8]) {
self.nlas.as_slice().emit(buffer)
}

fn buffer_len(&self) -> usize {
self.nlas.as_slice().buffer_len()
}
}

impl ParseableParametrized<[u8], GenlHeader> for Wireguard {
fn parse_with_param(buf: &[u8], header: GenlHeader) -> Result<Self, DecodeError> {
Ok(Self {
cmd: header.cmd.try_into()?,
nlas: parse_nlas(buf)?,
})
}
}

fn parse_nlas(buf: &[u8]) -> Result<Vec<WgDeviceAttrs>, DecodeError> {
let mut nlas = Vec::new();
let error_msg = "failed to parse message attributes";
for nla in NlasIterator::new(buf) {
let nla = &nla.context(error_msg)?;
let parsed = WgDeviceAttrs::parse(nla).context(error_msg)?;
nlas.push(parsed);
}
Ok(nlas)
}
80 changes: 80 additions & 0 deletions netlink-packet-wireguard/src/nlas/allowedip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::{constants::*, raw::*};
Leo1003 marked this conversation as resolved.
Show resolved Hide resolved
use anyhow::Context;
use byteorder::{ByteOrder, NativeEndian};
use libc::{in6_addr, in_addr};
use netlink_packet_utils::{
nla::{Nla, NlaBuffer},
parsers::*,
traits::*,
DecodeError,
};
use std::{
mem::{size_of, size_of_val},
net::IpAddr,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WgAllowedIpAttrs {
Unspec(Vec<u8>),
Family(u16),
IpAddr(IpAddr),
little-dude marked this conversation as resolved.
Show resolved Hide resolved
Cidr(u8),
}

impl Nla for WgAllowedIpAttrs {
fn value_len(&self) -> usize {
match self {
WgAllowedIpAttrs::Unspec(bytes) => bytes.len(),
WgAllowedIpAttrs::Family(v) => size_of_val(v),
WgAllowedIpAttrs::IpAddr(v) => match *v {
IpAddr::V4(_) => size_of::<in_addr>(),
IpAddr::V6(_) => size_of::<in6_addr>(),
},
WgAllowedIpAttrs::Cidr(v) => size_of_val(v),
}
}

fn kind(&self) -> u16 {
match self {
WgAllowedIpAttrs::Unspec(_) => WGALLOWEDIP_A_UNSPEC,
WgAllowedIpAttrs::Family(_) => WGALLOWEDIP_A_FAMILY,
WgAllowedIpAttrs::IpAddr(_) => WGALLOWEDIP_A_IPADDR,
WgAllowedIpAttrs::Cidr(_) => WGALLOWEDIP_A_CIDR_MASK,
}
}

fn emit_value(&self, buffer: &mut [u8]) {
match self {
WgAllowedIpAttrs::Unspec(bytes) => buffer.copy_from_slice(bytes),
WgAllowedIpAttrs::Family(v) => NativeEndian::write_u16(buffer, *v),
WgAllowedIpAttrs::IpAddr(v) => match v {
IpAddr::V4(addr) => emit_in_addr(addr, buffer),
IpAddr::V6(addr) => emit_in6_addr(addr, buffer),
},
WgAllowedIpAttrs::Cidr(v) => buffer[0] = *v,
}
}
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>> for WgAllowedIpAttrs {
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
WGALLOWEDIP_A_UNSPEC => Self::Unspec(payload.to_vec()),
WGALLOWEDIP_A_FAMILY => {
Self::Family(parse_u16(payload).context("invalid WGALLOWEDIP_A_FAMILY value")?)
}
WGALLOWEDIP_A_IPADDR => {
if payload.len() == size_of::<in_addr>() {
Self::IpAddr(IpAddr::from(parse_in_addr(payload)?))
} else if payload.len() == size_of::<in6_addr>() {
Self::IpAddr(IpAddr::from(parse_in6_addr(payload)?))
} else {
return Err(DecodeError::from("invalid WGALLOWEDIP_A_IPADDR value"));
}
}
WGALLOWEDIP_A_CIDR_MASK => Self::Cidr(payload[0]),
kind => return Err(DecodeError::from(format!("invalid NLA kind: {}", kind))),
})
}
}