Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions examples/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{net::SocketAddr, str::FromStr};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
str::FromStr,
};

use defguard_wireguard_rs::{
InterfaceConfiguration, WGApi, WireguardInterfaceApi, key::Key, net::IpAddrMask, peer::Peer,
Expand All @@ -7,6 +10,8 @@ use x25519_dalek::{EphemeralSecret, PublicKey};

#[cfg(not(target_os = "netbsd"))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

// Create new API object for interface
let ifname: String = if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") {
"wg0".into()
Expand All @@ -31,7 +36,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

log::info!("endpoint");
// Your WireGuard server endpoint which client connects to
let endpoint: SocketAddr = "10.10.10.10:55001".parse().unwrap();
let endpoint = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 10, 10, 10)), 55001);
// Peer endpoint and interval
peer.endpoint = Some(endpoint);
peer.persistent_keepalive_interval = Some(25);
Expand Down
24 changes: 12 additions & 12 deletions src/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::io::{Cursor, Error as IoError};
use std::net::IpAddr;
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
use std::{
fmt::Write as _,
fs::{File, read_dir},
io::Write,
path::Path,
Expand Down Expand Up @@ -282,7 +283,7 @@ impl<'a> DnsConfig<'a> {
fn resolvconf_stdin(&self) -> String {
let mut stdin = String::new();
for server in self.servers {
stdin.push_str(&format!("nameserver {server}\n"));
let _ = writeln!(stdin, "nameserver {server}");
}
// Routing-only domains have to be declared as search domains here, as that is the only way
// of telling resolvconf which domains this interface resolves.
Expand All @@ -295,7 +296,7 @@ impl<'a> DnsConfig<'a> {
if !domains.is_empty() {
// resolv.conf(5) holds a single search list, and a second `search` line overrides the
// first one rather than extending it.
stdin.push_str(&format!("search {}\n", domains.join(" ")));
let _ = writeln!(stdin, "search {}", domains.join(" "));
}
stdin
}
Expand Down Expand Up @@ -478,16 +479,15 @@ fn systemd_resolved_available() -> bool {
debug!("{RESOLVED_RUNTIME_DIR} does not exist, assuming systemd-resolved is not running");
return false;
}
match get_command_path(RESOLVECTL) {
Ok(Some(_)) => true,
_ => {
warn!(
"systemd-resolved appears to be running, but the `{RESOLVECTL}` command could \
not be found in PATH. Falling back to `{RESOLVCONF}`, which cannot configure \
split DNS on this host."
);
false
}
if let Ok(Some(_)) = get_command_path(RESOLVECTL) {
true
} else {
warn!(
"systemd-resolved appears to be running, but the `{RESOLVECTL}` command could not be \
found in PATH. Falling back to `{RESOLVCONF}`, which cannot configure split DNS on \
this host."
);
false
}
}

Expand Down
82 changes: 67 additions & 15 deletions src/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,10 @@ pub(crate) fn set_link_up(if_name: &str) -> NetlinkResult<()> {
Ok(())
}

#[cfg(test)]
/// Get default route for a given address family.
pub(crate) fn get_gateway(address_family: AddressFamily) -> NetlinkResult<Option<IpAddr>> {
pub(crate) fn get_gateway(ip_version: IpVersion) -> NetlinkResult<Option<IpAddr>> {
let header = RouteHeader {
address_family,
address_family: ip_version.address_family(),
table: RouteHeader::RT_TABLE_MAIN,
// protocol: RouteProtocol::Boot, // doesn't filter
// scope: RouteScope::Universe, // doesn't filter
Expand Down Expand Up @@ -522,13 +521,70 @@ pub(crate) fn get_gateway(address_family: AddressFamily) -> NetlinkResult<Option
}
}
} else {
debug!("unknown nlmsg response")
debug!("unknown nlmsg response");
}
}

Ok(None)
}

/// Convert an [`IpAddr`] into a netlink [`RouteAddress`].
fn route_address(address: IpAddr) -> RouteAddress {
match address {
IpAddr::V4(ipv4) => RouteAddress::Inet(ipv4),
IpAddr::V6(ipv6) => RouteAddress::Inet6(ipv6),
}
}

/// Set a route to `dest` through `gateway`, replacing an existing one, if any.
///
/// When `is_blackhole` is set, traffic to `dest` is dropped and `gateway` is ignored.
pub(crate) fn add_gateway(
dest: &IpAddrMask,
gateway: IpAddr,
is_blackhole: bool,
) -> NetlinkResult<()> {
if !is_blackhole && dest.address.is_ipv4() != gateway.is_ipv4() {
error!("Destination {dest} and gateway {gateway} IP versions don't match");
return Err(NetlinkError::AddRouteError);
}

let mut message = RouteMessage::default();
message.header = RouteHeader {
address_family: dest.address_family(),
destination_prefix_length: dest.cidr,
table: RouteHeader::RT_TABLE_MAIN,
scope: RouteScope::Universe,
kind: if is_blackhole {
RouteType::BlackHole
} else {
RouteType::Unicast
},
protocol: RouteProtocol::Boot,
..Default::default()
};
message
.attributes
.push(RouteAttribute::Destination(route_address(dest.address)));
if !is_blackhole {
message
.attributes
.push(RouteAttribute::Gateway(route_address(gateway)));
}

match netlink_request(
RouteNetlinkMessage::NewRoute(message),
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE,
NETLINK_ROUTE,
) {
Ok(_msg) => Ok(()),
Err(err) => {
error!("Failed to set gateway for {dest}: {err}");
Err(NetlinkError::AddRouteError)
}
}
}

/// Add a route for an interface.
pub(crate) fn add_route(
ifname: &str,
Expand All @@ -545,10 +601,6 @@ pub(crate) fn add_route(
protocol: RouteProtocol::Boot,
..Default::default()
};
let route_address = match address.address {
IpAddr::V4(ipv4) => RouteAddress::Inet(ipv4),
IpAddr::V6(ipv6) => RouteAddress::Inet6(ipv6),
};
message.header = header;
let Some(interface_index) = get_interface_index(ifname)? else {
error!("Failed to add WireGuard interface route interface {ifname} index not found");
Expand All @@ -560,7 +612,7 @@ pub(crate) fn add_route(
.push(RouteAttribute::Oif(interface_index));
message
.attributes
.push(RouteAttribute::Destination(route_address));
.push(RouteAttribute::Destination(route_address(address.address)));
if let Some(table) = table {
message.attributes.push(RouteAttribute::Table(table));
}
Expand Down Expand Up @@ -856,7 +908,7 @@ mod tests {
Err(NetlinkError::AttributeNotFound)
}

#[ignore]
#[ignore = "destructive"]
#[test]
fn docker_networking() {
const IF_NAME: &str = "wg0";
Expand Down Expand Up @@ -885,11 +937,12 @@ mod tests {
delete_interface(IF_NAME).unwrap();
}

#[ignore]
#[ignore = "destructive"]
#[test]
fn docker_peers() {
use x25519_dalek::{EphemeralSecret, PublicKey};

const IF_NAME: &str = "wg0";
const MAX_PEERS: usize = 1600;

let secret = EphemeralSecret::random();
Expand All @@ -906,7 +959,6 @@ mod tests {
host.peers.insert(key, peer);
}

const IF_NAME: &str = "wg0";
create_interface(IF_NAME).unwrap();
set_host(IF_NAME, &host).unwrap();

Expand All @@ -917,18 +969,18 @@ mod tests {
delete_interface(IF_NAME).unwrap();
}

#[ignore]
#[ignore = "destructive"]
#[test]
fn docker_gateway() {
let gateway = get_gateway(AddressFamily::Inet).unwrap();
let gateway = get_gateway(IpVersion::IPv4).unwrap();
assert!(gateway.is_some());
}

// For this test, execute:
// - `ip link add dev wg0 type wireguard`
// - `ip link set up dev wg0`
// - `ip route add default dev wg0 scope link table 51820`
#[ignore]
#[ignore = "destructive"]
#[test]
fn docker_route() {
let count = count_routes(IpVersion::IPv4, 51820).unwrap();
Expand Down
Loading