diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..2d33829
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,16 @@
+changelog:
+ exclude:
+ labels:
+ - ignore-for-release
+ categories:
+ - title: Breaking Changes ðŸ›
+ labels:
+ - Semver-Major
+ - breaking-change
+ - title: Exciting New Features 🎉
+ labels:
+ - Semver-Minor
+ - enhancement
+ - title: Other Changes
+ labels:
+ - "*"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..10f00ef
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,18 @@
+name: Create a new GitHub release
+on:
+ push:
+ tags:
+ - v*.*.*
+
+jobs:
+ create-release:
+ runs-on: self-hosted
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Create release
+ uses: softprops/action-gh-release@v1
+ if: startsWith(github.ref, 'refs/tags/')
+ with:
+ draft: true
+ generate_release_notes: true
diff --git a/.gitignore b/.gitignore
index ea8c4bf..c403c34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/target
+.idea/
diff --git a/Cargo.lock b/Cargo.lock
index 14d68f9..5da0d38 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -86,6 +86,23 @@ dependencies = [
"syn",
]
+[[package]]
+name = "defguard_wireguard_rs"
+version = "0.2.0"
+dependencies = [
+ "base64",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-generic",
+ "netlink-packet-route",
+ "netlink-packet-utils",
+ "netlink-packet-wireguard",
+ "netlink-sys",
+ "nix",
+ "thiserror",
+ "x25519-dalek",
+]
+
[[package]]
name = "fiat-crypto"
version = "0.2.1"
@@ -303,18 +320,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.48"
+version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
+checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.48"
+version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
+checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
@@ -333,23 +350,6 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
-[[package]]
-name = "wireguard_rs"
-version = "0.1.0"
-dependencies = [
- "base64",
- "log",
- "netlink-packet-core",
- "netlink-packet-generic",
- "netlink-packet-route",
- "netlink-packet-utils",
- "netlink-packet-wireguard",
- "netlink-sys",
- "nix",
- "thiserror",
- "x25519-dalek",
-]
-
[[package]]
name = "x25519-dalek"
version = "2.0.0"
diff --git a/Cargo.toml b/Cargo.toml
index 7a47d5a..8b5ecea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,8 +1,14 @@
[package]
-name = "wireguard_rs"
-version = "0.1.0"
+name = "defguard_wireguard_rs"
+version = "0.2.0"
edition = "2021"
-
+description = "A unified multi-platform high-level API for managing WireGuard interfaces"
+license = "Apache-2.0"
+readme = "README.md"
+homepage = "https://github.com/DefGuard/wireguard-rs"
+repository = "https://github.com/DefGuard/wireguard-rs"
+keywords = ["wireguard", "network", "vpn"]
+categories = ["network-programming"]
[dependencies]
base64 = "0.21"
diff --git a/README.md b/README.md
index 393967d..965c132 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-**wireguard-rs** is a Rust library providing a unified high-level API for managing WireGuard interfaces using native OS kernel and userspace WireGuard protocol implementations.
+**defguard_wireguard_rs** is a multi-platform Rust library providing a unified high-level API for managing WireGuard interfaces using native OS kernel and userspace WireGuard protocol implementations.
It can be used to create your own [WireGuard:tm:](https://www.wireguard.com/) VPN servers or clients for secure and private networking.
It was developed as part of [defguard](https://github.com/defguard/defguard) security platform and used in the [gateway/server](https://github.com/defguard/gateway) as well as [desktop client](https://github.com/defguard/client).
@@ -18,6 +18,11 @@ It was developed as part of [defguard](https://github.com/defguard/defguard) sec
- macOS
- FreeBSD
+### Note on `wireguard-go`
+If you intend to use the userspace WireGuard implementation you should note that currently the library assumes
+that the `wireguard-go` binary will be available at runtime. There are some sanity checks when instantiating the API,
+but installing it is outside the scope of this project.
+
## Examples
* Client: https://github.com/DefGuard/wireguard-rs/blob/main/examples/client.rs
diff --git a/docs/header.png b/docs/header.png
index 6e4d1b9..0a1b940 100644
Binary files a/docs/header.png and b/docs/header.png differ
diff --git a/examples/client.rs b/examples/client.rs
index b00c0bf..a6cf5bd 100644
--- a/examples/client.rs
+++ b/examples/client.rs
@@ -1,7 +1,7 @@
use std::{net::SocketAddr, str::FromStr};
-use wireguard_rs::{
- wgapi::WGApi, InterfaceConfiguration, IpAddrMask, Key, Peer, WireguardInterfaceApi,
+use defguard_wireguard_rs::{
+ host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration, WGApi, WireguardInterfaceApi,
};
use x25519_dalek::{EphemeralSecret, PublicKey};
diff --git a/examples/server.rs b/examples/server.rs
index c1e8e1b..e599d8a 100644
--- a/examples/server.rs
+++ b/examples/server.rs
@@ -1,7 +1,7 @@
use std::str::FromStr;
-use wireguard_rs::{
- wgapi::WGApi, InterfaceConfiguration, IpAddrMask, Key, Peer, WireguardInterfaceApi,
+use defguard_wireguard_rs::{
+ host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration, WGApi, WireguardInterfaceApi,
};
use x25519_dalek::{EphemeralSecret, PublicKey};
diff --git a/examples/userspace.rs b/examples/userspace.rs
index 91fce48..5844c7a 100644
--- a/examples/userspace.rs
+++ b/examples/userspace.rs
@@ -1,11 +1,12 @@
+use defguard_wireguard_rs::{
+ host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration, WireguardApiUserspace,
+ WireguardInterfaceApi,
+};
use std::{
io::{stdin, stdout, Read, Write},
net::SocketAddr,
str::FromStr,
};
-use wireguard_rs::{
- InterfaceConfiguration, IpAddrMask, Key, Peer, WireguardApiUserspace, WireguardInterfaceApi,
-};
use x25519_dalek::{EphemeralSecret, PublicKey};
fn pause() {
diff --git a/src/bsd/nvlist.rs b/src/bsd/nvlist.rs
index e81f897..a5de851 100644
--- a/src/bsd/nvlist.rs
+++ b/src/bsd/nvlist.rs
@@ -1,5 +1,5 @@
-// https://github.com/freebsd/freebsd-src/tree/main/sys/contrib/libnv
-// https://github.com/freebsd/freebsd-src/blob/main/sys/sys/nv.h
+//! `nvlist` implementation in Rust.
+
use std::{error::Error, ffi::CStr, fmt};
/// `NV_HEADER_SIZE` is for both: `nvlist_header` and `nvpair_header`.
diff --git a/src/error.rs b/src/error.rs
index 265f599..d575d4f 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,3 +1,5 @@
+//! Interface management errors
+
use thiserror::Error;
#[derive(Debug, Error)]
diff --git a/src/host.rs b/src/host.rs
index dce4f1a..b59bfb2 100644
--- a/src/host.rs
+++ b/src/host.rs
@@ -1,3 +1,5 @@
+//! Host interface configuration
+
use std::{
collections::HashMap,
io::{self, BufRead, BufReader, Read},
@@ -14,6 +16,7 @@ use netlink_packet_wireguard::{
use crate::{key::Key, net::IpAddrMask};
+/// WireGuard peer representation.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct Peer {
pub public_key: Key,
@@ -28,6 +31,7 @@ pub struct Peer {
}
impl Peer {
+ /// Create new `Peer` with a given `public_key`.
#[must_use]
pub fn new(public_key: Key) -> Self {
Self {
@@ -153,6 +157,7 @@ impl Peer {
}
}
+/// WireGuard host representation.
#[derive(Debug, Default)]
pub struct Host {
pub listen_port: u16,
@@ -162,6 +167,7 @@ pub struct Host {
}
impl Host {
+ /// Create new `Host` with a given `listen_port` and `private_key`.
#[must_use]
pub fn new(listen_port: u16, private_key: Key) -> Self {
Self {
diff --git a/src/key.rs b/src/key.rs
index 50e0aeb..f9cb696 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -1,3 +1,5 @@
+//! Public key utilities
+
use std::{
error, fmt,
hash::{Hash, Hasher},
@@ -8,6 +10,7 @@ use base64::{prelude::BASE64_STANDARD, DecodeError, Engine};
const KEY_LENGTH: usize = 32;
+/// WireGuard key representation in binary form.
#[derive(Clone, Default)]
pub struct Key([u8; KEY_LENGTH]);
@@ -31,6 +34,7 @@ impl fmt::Display for KeyError {
}
impl Key {
+ /// Create a new key from buffer.
#[must_use]
pub fn new(buf: [u8; KEY_LENGTH]) -> Self {
Self(buf)
@@ -46,6 +50,7 @@ impl Key {
self.0.as_slice()
}
+ /// Converts `Key` to `String` of lower case hexadecimal digits.
#[must_use]
pub fn to_lower_hex(&self) -> String {
let mut hex = String::with_capacity(64);
@@ -62,6 +67,11 @@ impl Key {
hex
}
+ /// Converts a text string of hexadecimal digits to `Key`.
+ ///
+ /// # Errors
+ /// Will return `KeyError` if text string has wrong length,
+ /// or contains an invalid character.
pub fn decode>(hex: T) -> Result {
let hex = hex.as_ref();
let length = hex.len();
diff --git a/src/lib.rs b/src/lib.rs
index 686b86f..70b46cb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,54 @@
+//! # `defguard_wireguard_rs`
+//!
+//! `defguard_wireguard_rs` is a multi-platform Rust library providing a unified high-level API
+//! for managing WireGuard interfaces using native OS kernel and userspace WireGuard protocol implementations.
+//!
+//! It can be used to create your own [WireGuard:tm:](https://www.wireguard.com/) VPN servers or clients for secure and private networking.
+//!
+//! It was developed as part of [defguard](https://github.com/defguard/defguard) security platform and used in the [gateway/server](https://github.com/defguard/gateway) as well as [desktop client](https://github.com/defguard/client).
+//!
+//! ## Example
+//!
+//! ```no_run
+//! use x25519_dalek::{EphemeralSecret, PublicKey};
+//! use defguard_wireguard_rs::{InterfaceConfiguration, WGApi, WireguardInterfaceApi, host::Peer};
+//! # use defguard_wireguard_rs::error::WireguardInterfaceError;
+//!
+//! // Create new API struct for interface
+//! let ifname: String = if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") {
+//! "wg0".into()
+//! } else {
+//! "utun3".into()
+//! };
+//! let wgapi = WGApi::new(ifname.clone(), false)?;
+//!
+//! // Create host interfaces
+//! wgapi.create_interface()?;
+//!
+//! // Configure host interface
+//! let interface_config = InterfaceConfiguration {
+//! name: ifname.clone(),
+//! prvkey: "AAECAwQFBgcICQoLDA0OD/Dh0sO0pZaHeGlaSzwtHg8=".to_string(),
+//! address: "10.6.0.30".to_string(),
+//! port: 12345,
+//! peers: vec![],
+//! };
+//! wgapi.configure_interface(&interface_config)?;
+//!
+//! // Create, add & remove peers
+//! for _ in 0..32 {
+//! let secret = EphemeralSecret::random();
+//! let key = PublicKey::from(&secret);
+//! let peer = Peer::new(key.as_ref().try_into().unwrap());
+//! wgapi.configure_peer(&peer)?;
+//! wgapi.remove_peer(&peer.public_key)?;
+//! }
+//!
+//! // Remove host interface
+//! wgapi.remove_interface()?;
+//! # Ok::<(), WireguardInterfaceError>(())
+//! ```
+
#[cfg(target_os = "freebsd")]
pub mod bsd;
pub mod error;
@@ -6,7 +57,7 @@ pub mod key;
pub mod net;
#[cfg(target_os = "linux")]
pub mod netlink;
-pub mod wgapi;
+mod wgapi;
#[cfg(target_os = "freebsd")]
mod wgapi_freebsd;
@@ -21,23 +72,24 @@ extern crate log;
use std::process::Output;
-// public reexports
+use self::{
+ error::WireguardInterfaceError,
+ host::{Host, Peer},
+ key::Key,
+ net::IpAddrMask,
+};
+
+// public re-exports
+pub use wgapi::WGApi;
#[cfg(target_os = "freebsd")]
pub use wgapi_freebsd::WireguardApiFreebsd;
#[cfg(target_os = "linux")]
pub use wgapi_linux::WireguardApiLinux;
#[cfg(target_family = "unix")]
pub use wgapi_userspace::WireguardApiUserspace;
-pub use {
- self::error::WireguardInterfaceError,
- host::{Host, Peer},
- key::Key,
- net::{IpAddrMask, IpAddrParseError},
- wgapi::WGApi,
- wireguard_interface::WireguardInterfaceApi,
-};
+pub use wireguard_interface::WireguardInterfaceApi;
-/// Wireguard Interface configuration
+/// Host WireGuard interface configuration
#[derive(Debug, Clone)]
pub struct InterfaceConfiguration {
pub name: String,
@@ -63,8 +115,8 @@ impl TryFrom<&InterfaceConfiguration> for Host {
}
}
-/// Util function which checks external command output status.
-pub fn check_command_output_status(output: Output) -> Result<(), WireguardInterfaceError> {
+/// Utility function which checks external command output status.
+fn check_command_output_status(output: Output) -> Result<(), WireguardInterfaceError> {
if !output.status.success() {
let stdout = String::from_utf8(output.stdout).expect("Invalid UTF8 sequence in stdout");
let stderr = String::from_utf8(output.stderr).expect("Invalid UTF8 sequence in stderr");
diff --git a/src/net.rs b/src/net.rs
index b96319e..8f1bef2 100644
--- a/src/net.rs
+++ b/src/net.rs
@@ -1,3 +1,5 @@
+//! Network address utilities
+
use std::{error, fmt, net::IpAddr, str::FromStr};
#[cfg(target_os = "linux")]
@@ -6,6 +8,7 @@ use netlink_packet_wireguard::{
nlas::{WgAllowedIp, WgAllowedIpAttrs},
};
+/// IP address with CIDR.
#[derive(Debug, PartialEq, Clone)]
pub struct IpAddrMask {
// IP v4 or v6
diff --git a/src/netlink.rs b/src/netlink.rs
index 8947a35..20c6670 100644
--- a/src/netlink.rs
+++ b/src/netlink.rs
@@ -1,3 +1,5 @@
+//! Netlink utilities for controlling network interfaces on Linux
+
use std::{
fmt::Debug,
io::ErrorKind,
@@ -68,6 +70,7 @@ impl From for WireguardInterfaceError {
}
}
+/// Wrapper `Result` type for Netlink operations
pub type NetlinkResult = Result;
macro_rules! get_nla_value {
@@ -92,7 +95,7 @@ impl Key {
}
}
-pub fn netlink_request_genl(
+fn netlink_request_genl(
mut message: GenlMessage,
flags: u16,
) -> NetlinkResult>>>
@@ -126,7 +129,7 @@ where
netlink_request(message, flags, NETLINK_GENERIC)
}
-pub fn netlink_request(
+fn netlink_request(
message: I,
flags: u16,
socket: isize,
@@ -280,6 +283,7 @@ fn set_address(ifindex: u32, address: &IpAddrMask) -> NetlinkResult<()> {
Ok(())
}
+/// Set IP address of a WireGuard network interface
pub fn address_interface(ifname: &str, address: &IpAddrMask) -> NetlinkResult<()> {
let mut message = LinkMessage::default();
message.nlas.push(Nla::IfName(ifname.into()));
@@ -314,7 +318,7 @@ pub fn address_interface(ifname: &str, address: &IpAddrMask) -> NetlinkResult<()
Ok(())
}
-/// Delete WireGuard interface.
+/// Delete WireGuard interface
pub fn delete_interface(ifname: &str) -> NetlinkResult<()> {
let mut message = LinkMessage::default();
message.nlas.push(Nla::IfName(ifname.into()));
@@ -334,6 +338,7 @@ pub fn delete_interface(ifname: &str) -> NetlinkResult<()> {
Ok(())
}
+/// Read host interface data
pub fn get_host(ifname: &str) -> NetlinkResult {
debug!("Reading Netlink data for interface {ifname}");
let genlmsg = GenlMessage::from_payload(Wireguard {
@@ -358,6 +363,7 @@ pub fn get_host(ifname: &str) -> NetlinkResult {
Ok(host)
}
+/// Perform interface configuration
pub fn set_host(ifname: &str, host: &Host) -> NetlinkResult<()> {
let genlmsg = GenlMessage::from_payload(Wireguard {
cmd: WireguardCmd::SetDevice,
@@ -367,6 +373,7 @@ pub fn set_host(ifname: &str, host: &Host) -> NetlinkResult<()> {
Ok(())
}
+/// Save or update WireGuard peer configuration
pub fn set_peer(ifname: &str, peer: &Peer) -> NetlinkResult<()> {
let genlmsg = GenlMessage::from_payload(Wireguard {
cmd: WireguardCmd::SetDevice,
@@ -376,6 +383,7 @@ pub fn set_peer(ifname: &str, peer: &Peer) -> NetlinkResult<()> {
Ok(())
}
+/// Delete a WireGuard peer from interface
pub fn delete_peer(ifname: &str, public_key: &Key) -> NetlinkResult<()> {
let genlmsg = GenlMessage::from_payload(Wireguard {
cmd: WireguardCmd::SetDevice,
diff --git a/src/wgapi.rs b/src/wgapi.rs
index 31e1296..f3e3474 100644
--- a/src/wgapi.rs
+++ b/src/wgapi.rs
@@ -1,3 +1,5 @@
+//! Shared multi-platform management API abstraction
+
#[cfg(target_os = "freebsd")]
use crate::WireguardApiFreebsd;
#[cfg(target_os = "linux")]
@@ -9,9 +11,17 @@ use crate::{
WireguardInterfaceError,
};
+/// Shared multi-platform WireGuard management API
+///
+/// This struct adds an additional level of abstraction and can be used
+/// to detect the correct API implementation for most common platforms.
pub struct WGApi(Box);
impl WGApi {
+ /// Create new instance of `WGApi`.
+ ///
+ /// # Errors
+ /// Will return `WireguardInterfaceError` is platform is not supported.
pub fn new(ifname: String, userspace: bool) -> Result {
if userspace {
if cfg!(target_family = "unix") {
diff --git a/src/wgapi_userspace.rs b/src/wgapi_userspace.rs
index c5a0eca..f9ea601 100644
--- a/src/wgapi_userspace.rs
+++ b/src/wgapi_userspace.rs
@@ -23,6 +23,10 @@ pub struct WireguardApiUserspace {
}
impl WireguardApiUserspace {
+ /// Create new instance of `WireguardApiUserspace`.
+ ///
+ /// # Errors
+ /// Will return `WireguardInterfaceError` if `wireguard-go` can't be found.
pub fn new(ifname: String) -> Result {
// check that `wireguard-go` is available
Command::new(USERSPACE_EXECUTABLE).arg("--version").output().map_err(|err| {
@@ -37,6 +41,7 @@ impl WireguardApiUserspace {
format!("/var/run/wireguard/{}.sock", self.ifname)
}
+ /// Create UNIX socket to communicate with `wireguard-go`.
fn socket(&self) -> io::Result {
let path = self.socket_path();
let socket = UnixStream::connect(path)?;
@@ -64,6 +69,7 @@ impl WireguardApiUserspace {
0
}
+ /// Read host information using user-space API.
pub fn read_host(&self) -> io::Result {
debug!("Reading host interface info");
let mut socket = self.socket()?;
@@ -71,19 +77,20 @@ impl WireguardApiUserspace {
Host::parse_uapi(socket)
}
+ /// Write host information using user-space API.
pub fn write_host(&self, host: &Host) -> io::Result<()> {
let mut socket = self.socket()?;
socket.write_all(b"set=1\n")?;
socket.write_all(host.as_uapi().as_bytes())?;
socket.write_all(b"\n")?;
- if Self::parse_errno(socket) != 0 {
+ if Self::parse_errno(socket) == 0 {
+ Ok(())
+ } else {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"write configuration error",
))
- } else {
- Ok(())
}
}
}
@@ -153,10 +160,10 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
socket.write_all(peer.as_uapi_update().as_bytes())?;
socket.write_all(b"\n")?;
- if Self::parse_errno(socket) != 0 {
- Err(WireguardInterfaceError::PeerConfigurationError)
- } else {
+ if Self::parse_errno(socket) == 0 {
Ok(())
+ } else {
+ Err(WireguardInterfaceError::PeerConfigurationError)
}
}
@@ -172,10 +179,10 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
)?;
socket.write_all(b"\n")?;
- if Self::parse_errno(socket) != 0 {
- Err(WireguardInterfaceError::PeerConfigurationError)
- } else {
+ if Self::parse_errno(socket) == 0 {
Ok(())
+ } else {
+ Err(WireguardInterfaceError::PeerConfigurationError)
}
}