#[cfg(all(test, not(target_os = "macos"), not(target_os = "windows")))]
mod tests {
use crate::device::{DefaultDeviceTransports, Device, DeviceBuilder};
use crate::udp::socket::UdpSocketFactory;
use crate::x25519::{PublicKey, StaticSecret};
use base64::Engine as _;
use base64::prelude::BASE64_STANDARD;
use hex::encode;
use rand_core::{OsRng, RngCore};
use std::fmt::Write as _;
use std::io::{BufRead, BufReader, Read, Write};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::process::Command;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
use tokio::net::UnixStream;
static NEXT_IFACE_IDX: AtomicUsize = AtomicUsize::new(100); static NEXT_PORT: AtomicUsize = AtomicUsize::new(61111); static NEXT_IP: AtomicUsize = AtomicUsize::new(0xc0000200); static NEXT_IP_V6: AtomicUsize = AtomicUsize::new(0);
fn next_ip() -> IpAddr {
IpAddr::V4(Ipv4Addr::from(
NEXT_IP.fetch_add(1, Ordering::Relaxed) as u32
))
}
fn next_ip_v6() -> IpAddr {
let addr = 0x2001_0db8_0000_0000_0000_0000_0000_0000_u128
+ u128::from(NEXT_IP_V6.fetch_add(1, Ordering::Relaxed) as u32);
IpAddr::V6(Ipv6Addr::from(addr))
}
fn next_port() -> u16 {
NEXT_PORT.fetch_add(1, Ordering::Relaxed) as u16
}
struct AllowedIp {
ip: IpAddr,
cidr: u8,
}
struct Peer {
key: StaticSecret,
endpoint: SocketAddr,
allowed_ips: Vec<AllowedIp>,
container_name: Option<String>,
}
struct WGHandle {
_device: Device<DefaultDeviceTransports>,
name: String,
addr_v4: IpAddr,
addr_v6: IpAddr,
started: bool,
peers: Vec<Arc<Peer>>,
}
impl Drop for Peer {
fn drop(&mut self) {
if let Some(name) = &self.container_name {
Command::new("docker")
.args([
"stop", &name[5..],
])
.status()
.ok();
std::fs::remove_file(name).ok();
std::fs::remove_file(format!("{name}.ngx")).ok();
}
}
}
impl Peer {
fn new(endpoint: SocketAddr, allowed_ips: Vec<AllowedIp>) -> Peer {
Peer {
key: StaticSecret::random_from_rng(OsRng),
endpoint,
allowed_ips,
container_name: None,
}
}
fn gen_wg_conf(
&self,
local_key: &PublicKey,
local_addr: &IpAddr,
local_port: u16,
) -> String {
let mut conf = String::from("[Interface]\n");
for ip in &self.allowed_ips {
let _ = writeln!(conf, "Address = {}/{}", ip.ip, ip.cidr);
}
let _ = writeln!(conf, "ListenPort = {}", self.endpoint.port());
let _ = writeln!(
conf,
"PrivateKey = {}",
BASE64_STANDARD.encode(self.key.to_bytes())
);
let _ = writeln!(conf, "[Peer]");
let _ = writeln!(
conf,
"PublicKey = {}",
BASE64_STANDARD.encode(local_key.as_bytes())
);
let _ = writeln!(conf, "AllowedIPs = {local_addr}");
let _ = write!(conf, "Endpoint = 127.0.0.1:{local_port}");
conf
}
fn gen_nginx_conf(&self) -> String {
format!(
"server {{\n\
listen 80;\n\
listen [::]:80;\n\
location / {{\n\
return 200 '{}';\n\
}}\n\
}}",
encode(PublicKey::from(&self.key).as_bytes())
)
}
fn start_in_container(
&mut self,
local_key: &PublicKey,
local_addr: &IpAddr,
local_port: u16,
) {
let peer_config = self.gen_wg_conf(local_key, local_addr, local_port);
let peer_config_file = temp_path();
std::fs::write(&peer_config_file, peer_config).unwrap();
let nginx_config = self.gen_nginx_conf();
let nginx_config_file = format!("{peer_config_file}.ngx");
std::fs::write(&nginx_config_file, nginx_config).unwrap();
Command::new("docker")
.args([
"run", "-d", "--cap-add=NET_ADMIN", "--device=/dev/net/tun",
"--sysctl", "net.ipv6.conf.all.disable_ipv6=0",
"--sysctl",
"net.ipv6.conf.default.disable_ipv6=0",
"-p", &format!("{0}:{0}/udp", self.endpoint.port()),
"-v", &format!("{peer_config_file}:/wireguard/wg.conf"),
"-v", &format!("{nginx_config_file}:/etc/nginx/conf.d/default.conf"),
"--rm", "--name",
&peer_config_file[5..],
"vkrasnov/wireguard-test",
])
.status()
.expect("Failed to run docker");
self.container_name = Some(peer_config_file);
}
fn connect(&self) -> std::net::TcpStream {
let http_addr = SocketAddr::new(self.allowed_ips[0].ip, 80);
for _i in 0..5 {
let res = std::net::TcpStream::connect(http_addr);
if let Err(err) = res {
println!("failed to connect: {err:?}");
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
return res.unwrap();
}
panic!("failed to connect");
}
fn get_request(&self) -> String {
let mut tcp_conn = self.connect();
write!(
tcp_conn,
"GET / HTTP/1.1\nHost: localhost\nAccept: */*\nConnection: close\n\n"
)
.unwrap();
tcp_conn
.set_read_timeout(Some(std::time::Duration::from_secs(60)))
.ok();
let mut reader = BufReader::new(tcp_conn);
let mut line = String::new();
let mut response = String::new();
let mut len = 0usize;
if reader.read_line(&mut line).is_ok() && !line.starts_with("HTTP/1.1 200") {
return response;
}
line.clear();
while reader.read_line(&mut line).is_ok() {
if line.trim() == "" {
break;
}
{
let parsed_line: Vec<&str> = line.split(':').collect();
if parsed_line.len() < 2 {
return response;
}
let (key, val) = (parsed_line[0], parsed_line[1]);
if key.to_lowercase() == "content-length" {
len = match val.trim().parse() {
Err(_) => return response,
Ok(len) => len,
};
}
}
line.clear();
}
let mut buf = [0u8; 256];
while len > 0 {
let to_read = len.min(buf.len());
if reader.read_exact(&mut buf[..to_read]).is_err() {
return response;
}
response.push_str(&String::from_utf8_lossy(&buf[..to_read]));
len -= to_read;
}
response
}
}
impl WGHandle {
async fn init(addr_v4: IpAddr, addr_v6: IpAddr) -> WGHandle {
let tun_name = format!("utun{}", NEXT_IFACE_IDX.fetch_add(1, Ordering::Relaxed));
let uapi = crate::device::uapi::UapiServer::default_unix_socket(&tun_name, None, None)
.unwrap();
let device_builder = DeviceBuilder::new()
.create_tun(&tun_name)
.unwrap()
.with_udp(UdpSocketFactory)
.with_uapi(uapi);
let _device = device_builder.build().await.unwrap();
WGHandle {
_device,
name: tun_name,
addr_v4,
addr_v6,
started: false,
peers: vec![],
}
}
#[cfg(target_os = "macos")]
fn start(&mut self) {
Command::new("ifconfig")
.args(&[
&self.name,
&self.addr_v4.to_string(),
&self.addr_v4.to_string(),
"alias",
])
.status()
.expect("failed to assign ip to tunnel");
Command::new("ifconfig")
.args(&[
&self.name,
"inet6",
&self.addr_v6.to_string(),
"prefixlen",
"128",
"alias",
])
.status()
.expect("failed to assign ipv6 to tunnel");
Command::new("ifconfig")
.args(&[&self.name, "up"])
.status()
.expect("failed to start the tunnel");
self.started = true;
for p in &self.peers {
for r in &p.allowed_ips {
let inet_flag = match r.ip {
IpAddr::V4(_) => "-inet",
IpAddr::V6(_) => "-inet6",
};
Command::new("route")
.args(&[
"-q",
"-n",
"add",
inet_flag,
&format!("{}/{}", r.ip, r.cidr),
"-interface",
&self.name,
])
.status()
.expect("failed to add route");
}
}
}
#[cfg(target_os = "linux")]
fn start(&mut self) {
Command::new("ip")
.args([
"address",
"add",
&self.addr_v4.to_string(),
"dev",
&self.name,
])
.status()
.expect("failed to assign ip to tunnel");
Command::new("ip")
.args([
"address",
"add",
&self.addr_v6.to_string(),
"dev",
&self.name,
])
.status()
.expect("failed to assign ipv6 to tunnel");
Command::new("ip")
.args(["link", "set", "mtu", "1400", "up", "dev", &self.name])
.status()
.expect("failed to start the tunnel");
self.started = true;
for p in &self.peers {
for r in &p.allowed_ips {
Command::new("ip")
.args([
"route",
"add",
&format!("{}/{}", r.ip, r.cidr),
"dev",
&self.name,
])
.status()
.expect("failed to add route");
}
}
}
async fn wg_get(&self) -> String {
let path = format!("/var/run/wireguard/{}.sock", self.name);
let mut socket = UnixStream::connect(path)
.await
.expect("Must create UNIX socket to send UAPI requests");
socket.write_all(b"get=1\n\n").await.unwrap();
let mut ret = String::new();
let mut reader = tokio::io::BufReader::new(socket);
while reader.read_line(&mut ret).await.unwrap() > 1 {}
ret
}
async fn wg_set(&self, setting: &str) -> String {
let path = format!("/var/run/wireguard/{}.sock", self.name);
let mut socket = UnixStream::connect(path)
.await
.expect("Must create UNIX socket to send UAPI requests");
socket
.write_all(format!("set=1\n{setting}\n\n").as_bytes())
.await
.unwrap();
let mut ret = String::new();
let mut reader = tokio::io::BufReader::new(socket);
while reader.read_line(&mut ret).await.unwrap() > 1 {}
ret
}
async fn wg_set_port(&self, port: u16) -> String {
self.wg_set(&format!("listen_port={port}")).await
}
async fn wg_set_key(&self, key: StaticSecret) -> String {
self.wg_set(&format!("private_key={}", encode(key.to_bytes())))
.await
}
async fn wg_set_peer(
&self,
key: &PublicKey,
ep: &SocketAddr,
allowed_ips: &[AllowedIp],
) -> String {
let mut req = format!("public_key={}\nendpoint={}", encode(key.as_bytes()), ep);
for AllowedIp { ip, cidr } in allowed_ips {
let _ = write!(req, "\nallowed_ip={ip}/{cidr}");
}
self.wg_set(&req).await
}
async fn add_peer(&mut self, peer: Arc<Peer>) {
self.wg_set_peer(
&PublicKey::from(&peer.key),
&peer.endpoint,
&peer.allowed_ips,
)
.await;
self.peers.push(peer);
}
}
fn temp_path() -> String {
let mut path = String::from("/tmp/");
let mut buf = [0u8; 32];
OsRng.fill_bytes(&mut buf);
path.push_str(&encode(buf));
path
}
#[tokio::test]
#[ignore]
async fn test_wireguard_get() {
let wg = WGHandle::init("192.0.2.0".parse().unwrap(), "::2".parse().unwrap()).await;
let response = wg.wg_get().await;
assert!(
response.ends_with("errno=0\n\n"),
"Got response '{response}'"
);
}
#[tokio::test]
#[ignore]
async fn test_wireguard_set() {
let port = next_port();
let own_private_key = StaticSecret::random_from_rng(OsRng);
let wg = WGHandle::init("192.0.2.0".parse().unwrap(), "::2".parse().unwrap()).await;
assert!(wg.wg_get().await.ends_with("errno=0\n\n"));
assert_eq!(wg.wg_set_port(port).await, "errno=0\n\n");
assert_eq!(wg.wg_set_key(own_private_key.clone()).await, "errno=0\n\n");
let own_private_key = encode(own_private_key.as_bytes());
assert_eq!(
wg.wg_get().await,
format!("private_key={own_private_key}\nlisten_port={port}\nerrno=0\n\n",)
);
let peer_private_key = StaticSecret::random_from_rng(OsRng);
let peer_pub_key = PublicKey::from(&peer_private_key);
let endpoint = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(172, 0, 0, 1)), 50001);
let allowed_ips = [
AllowedIp {
ip: IpAddr::V4(Ipv4Addr::new(172, 0, 0, 2)),
cidr: 32,
},
AllowedIp {
ip: IpAddr::V6(Ipv6Addr::new(0xf120, 0, 0, 2, 2, 2, 0, 0)),
cidr: 100,
},
];
assert_eq!(
wg.wg_set_peer(&peer_pub_key, &endpoint, &allowed_ips).await,
"errno=0\n\n"
);
let wg_get = wg.wg_get().await;
let peer_pub_key = encode(peer_pub_key.as_bytes());
assert!(wg_get.contains(&format!("public_key={peer_pub_key}")));
assert!(wg_get.contains(&format!("endpoint={endpoint}")));
assert!(wg_get.contains(&format!(
"allowed_ip={}/{}",
allowed_ips[0].ip, allowed_ips[0].cidr
)));
assert!(wg_get.contains(&format!(
"allowed_ip={}/{}",
allowed_ips[1].ip, allowed_ips[1].cidr
)));
assert!(wg_get.contains("rx_bytes=0"));
assert!(wg_get.contains("tx_bytes=0"));
assert!(wg_get.contains(&format!("private_key={own_private_key}")));
assert!(wg_get.contains(&format!("listen_port={port}")));
assert!(wg_get.contains("errno=0"));
}
#[tokio::test]
#[ignore]
async fn test_wg_start_ipv4() {
let port = next_port();
let private_key = StaticSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
let addr_v4 = next_ip();
let addr_v6 = next_ip_v6();
let mut wg = WGHandle::init(addr_v4, addr_v6).await;
assert_eq!(wg.wg_set_port(port).await, "errno=0\n\n");
assert_eq!(wg.wg_set_key(private_key).await, "errno=0\n\n");
let mut peer = Peer::new(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), next_port()),
vec![AllowedIp {
ip: next_ip(),
cidr: 32,
}],
);
peer.start_in_container(&public_key, &addr_v4, port);
let peer = Arc::new(peer);
wg.add_peer(Arc::clone(&peer)).await;
wg.start();
let response = peer.get_request();
assert_eq!(response, encode(PublicKey::from(&peer.key).as_bytes()));
}
#[tokio::test]
#[ignore]
async fn test_wg_start_ipv6() {
let port = next_port();
let private_key = StaticSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
let addr_v4 = next_ip();
let addr_v6 = next_ip_v6();
let mut wg = WGHandle::init(addr_v4, addr_v6).await;
assert_eq!(wg.wg_set_port(port).await, "errno=0\n\n");
assert_eq!(wg.wg_set_key(private_key).await, "errno=0\n\n");
let mut peer = Peer::new(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), next_port()),
vec![AllowedIp {
ip: next_ip_v6(),
cidr: 128,
}],
);
peer.start_in_container(&public_key, &addr_v6, port);
let peer = Arc::new(peer);
wg.add_peer(Arc::clone(&peer)).await;
wg.start();
let response = peer.get_request();
assert_eq!(response, encode(PublicKey::from(&peer.key).as_bytes()));
}
#[tokio::test]
#[ignore]
#[cfg(target_os = "linux")] async fn test_wg_start_ipv6_endpoint() {
let port = next_port();
let private_key = StaticSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
let addr_v4 = next_ip();
let addr_v6 = next_ip_v6();
let mut wg = WGHandle::init(addr_v4, addr_v6).await;
assert_eq!(wg.wg_set_port(port).await, "errno=0\n\n");
assert_eq!(wg.wg_set_key(private_key).await, "errno=0\n\n");
let mut peer = Peer::new(
SocketAddr::new(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
next_port(),
),
vec![AllowedIp {
ip: next_ip_v6(),
cidr: 128,
}],
);
peer.start_in_container(&public_key, &addr_v6, port);
let peer = Arc::new(peer);
wg.add_peer(Arc::clone(&peer)).await;
wg.start();
let response = peer.get_request();
assert_eq!(response, encode(PublicKey::from(&peer.key).as_bytes()));
}
#[tokio::test]
#[ignore]
async fn test_wg_concurrent() {
let port = next_port();
let private_key = StaticSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
let addr_v4 = next_ip();
let addr_v6 = next_ip_v6();
let mut wg = WGHandle::init(addr_v4, addr_v6).await;
assert_eq!(wg.wg_set_port(port).await, "errno=0\n\n");
assert_eq!(wg.wg_set_key(private_key).await, "errno=0\n\n");
for _ in 0..5 {
let mut peer = Peer::new(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), next_port()),
vec![AllowedIp {
ip: next_ip(),
cidr: 32,
}],
);
peer.start_in_container(&public_key, &addr_v4, port);
let peer = Arc::new(peer);
wg.add_peer(Arc::clone(&peer)).await;
}
wg.start();
let mut threads = vec![];
for p in wg.peers {
let pub_key = PublicKey::from(&p.key);
threads.push(thread::spawn(move || {
for _ in 0..100 {
let response = p.get_request();
assert_eq!(response, encode(pub_key.as_bytes()));
}
}));
}
for t in threads {
t.join().unwrap();
}
}
#[tokio::test]
#[ignore]
async fn test_wg_concurrent_v6() {
let port = next_port();
let private_key = StaticSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
let addr_v4 = next_ip();
let addr_v6 = next_ip_v6();
let mut wg = WGHandle::init(addr_v4, addr_v6).await;
assert_eq!(wg.wg_set_port(port).await, "errno=0\n\n");
assert_eq!(wg.wg_set_key(private_key).await, "errno=0\n\n");
for _ in 0..5 {
let mut peer = Peer::new(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), next_port()),
vec![AllowedIp {
ip: next_ip_v6(),
cidr: 128,
}],
);
peer.start_in_container(&public_key, &addr_v6, port);
let peer = Arc::new(peer);
wg.add_peer(Arc::clone(&peer)).await;
}
wg.start();
let mut threads = vec![];
for p in wg.peers {
let pub_key = PublicKey::from(&p.key);
threads.push(thread::spawn(move || {
for _ in 0..100 {
let response = p.get_request();
assert_eq!(response, encode(pub_key.as_bytes()));
}
}));
}
for t in threads {
t.join().unwrap();
}
}
}