Restructuring and dependency version bump.

This commit is contained in:
Mathias Hall-Andersen
2020-03-29 18:21:48 +02:00
parent c4d2ad7a78
commit 12a7b371d4
22 changed files with 708 additions and 337 deletions

View File

@@ -1,6 +1,8 @@
#![feature(test)]
#![feature(weak_into_raw)]
extern crate alloc;
#[cfg(feature = "profiler")]
extern crate cpuprofiler;

View File

@@ -1,7 +1,13 @@
use hex;
use log::debug;
use rand::rngs::OsRng;
use rand::Rng;
// This provides a mock tunnel interface.
// Which enables unit tests where WireGuard interfaces
// are configured to match each other and a full test of:
//
// - Handshake
// - Transport encryption/decryption
//
// Can be executed.
use super::*;
use std::cmp::min;
use std::error::Error;
@@ -11,15 +17,16 @@ use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use super::super::tun::*;
#[derive(Debug)]
pub enum TunError {
Disconnected,
}
use hex;
use log::debug;
use rand::rngs::OsRng;
use rand::Rng;
pub struct TunTest {}
// Represents the "other end" (kernel/OS end) of the TUN connection:
//
// Used to send/receive packets to the mock WireGuard interface.
pub struct TunFakeIO {
id: u32,
store: bool,

View File

@@ -0,0 +1,12 @@
use super::super::tun::*;
mod dummy;
mod void;
#[derive(Debug)]
pub enum TunError {
Disconnected,
}
pub use dummy::*;
pub use void::*;

View File

@@ -0,0 +1,43 @@
/*
// This code provides a "void" implementation of the tunnel interface:
// The implementation never reads and immediately discards any write without error
//
// This is used during benchmarking and profiling of the inbound path.
use super::*;
pub struct VoidTun {}
pub struct VoidReader {}
pub struct VoidWriter {}
impl Tun for VoidTun {
type Writer = VoidWriter;
type Reader = VoidReader;
type Error = TunError;
}
impl Reader for VodReader {
type Error = TunError;
fn write(&self, src: &[u8]) -> Result<(), Self::Error> {
debug!(
"dummy::TUN({}) : write ({}, {})",
self.id,
src.len(),
hex::encode(src)
);
if self.store {
let m = src.to_owned();
match self.tx.lock().unwrap().send(m) {
Ok(_) => Ok(()),
Err(_) => Err(TunError::Disconnected),
}
} else {
Ok(())
}
}
}
*/

View File

@@ -26,9 +26,6 @@ pub use peer::Peer;
// represents a WireGuard interface
pub use wireguard::WireGuard;
#[cfg(test)]
pub use types::dummy_keypair;
#[cfg(test)]
use super::platform::dummy;

View File

@@ -1,4 +1,4 @@
use std::mem;
use core::mem;
// Implementation of RFC 6479.
// https://tools.ietf.org/html/rfc6479

View File

@@ -1,4 +1,4 @@
use std::mem;
use core::mem;
use byteorder::BigEndian;
use zerocopy::byteorder::U16;

View File

@@ -16,12 +16,13 @@ mod worker;
mod tests;
use messages::TransportHeader;
use std::mem;
use super::constants::REJECT_AFTER_MESSAGES;
use super::queue::ParallelQueue;
use super::types::*;
use core::mem;
pub const SIZE_TAG: usize = 16;
pub const SIZE_MESSAGE_PREFIX: usize = mem::size_of::<TransportHeader>();
pub const CAPACITY_MESSAGE_POSTFIX: usize = SIZE_TAG;

View File

@@ -15,11 +15,14 @@ use super::receive::ReceiveJob;
use super::send::SendJob;
use super::worker::JobUnion;
use std::mem;
use core::mem;
use core::ops::Deref;
use core::sync::atomic::AtomicBool;
use alloc::sync::Arc;
// TODO: consider no_std alternatives
use std::net::{IpAddr, SocketAddr};
use std::ops::Deref;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use arraydeque::{ArrayDeque, Wrapping};
use log;

View File

@@ -1,8 +1,8 @@
use arraydeque::ArrayDeque;
use spin::Mutex;
use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering};
use core::mem;
use core::sync::atomic::{AtomicUsize, Ordering};
use super::constants::INORDER_QUEUE_SIZE;

View File

@@ -7,9 +7,8 @@ use super::{REJECT_AFTER_MESSAGES, SIZE_TAG};
use super::super::{tun, udp, Endpoint};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305};
use spin::Mutex;
use zerocopy::{AsBytes, LayoutVerified};

View File

@@ -1,5 +1,6 @@
use super::ip::*;
// TODO: no_std alternatives
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use spin::RwLock;

View File

@@ -7,8 +7,8 @@ use super::{REJECT_AFTER_MESSAGES, SIZE_TAG};
use super::super::{tun, udp, Endpoint};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305};
use spin::Mutex;

View File

@@ -0,0 +1,424 @@
extern crate test;
use super::*;
use std::net::IpAddr;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use num_cpus;
use test::Bencher;
//
struct TransmissionCounter {
sent: AtomicUsize,
recv: AtomicUsize,
}
impl TransmissionCounter {
fn new() -> TransmissionCounter {
TransmissionCounter {
sent: AtomicUsize::new(0),
recv: AtomicUsize::new(0),
}
}
fn reset(&self) {
self.sent.store(0, Ordering::SeqCst);
self.recv.store(0, Ordering::SeqCst);
}
fn sent(&self) -> usize {
self.sent.load(Ordering::Acquire)
}
fn recv(&self) -> usize {
self.recv.load(Ordering::Acquire)
}
}
struct BencherCallbacks {}
impl Callbacks for BencherCallbacks {
type Opaque = Arc<TransmissionCounter>;
fn send(t: &Self::Opaque, size: usize, _sent: bool, _keypair: &Arc<KeyPair>, _counter: u64) {
t.sent.fetch_add(size, Ordering::SeqCst);
}
fn recv(t: &Self::Opaque, size: usize, _sent: bool, _keypair: &Arc<KeyPair>) {
t.recv.fetch_add(size, Ordering::SeqCst);
}
fn need_key(_t: &Self::Opaque) {}
fn key_confirmed(_t: &Self::Opaque) {}
}
#[cfg(feature = "profiler")]
use cpuprofiler::PROFILER;
#[cfg(feature = "profiler")]
fn profiler_stop() {
println!("Stopping profiler");
PROFILER.lock().unwrap().stop().unwrap();
}
#[cfg(feature = "profiler")]
fn profiler_start(name: &str) {
use std::path::Path;
// find first available path to save profiler output
let mut n = 0;
loop {
let path = format!("./{}-{}.profile", name, n);
if !Path::new(path.as_str()).exists() {
println!("Starting profiler: {}", path);
PROFILER.lock().unwrap().start(path).unwrap();
break;
};
n += 1;
}
}
#[bench]
fn bench_router_outbound(b: &mut Bencher) {
// 10 GB transmission per iteration
const BYTES_PER_ITER: usize = 100 * 1024 * 1024 * 1024;
// inner payload of IPv4 packet is 1440 bytes
const BYTES_PER_PACKET: usize = 1440;
// create device
let (_fake, _reader, tun_writer, _mtu) = dummy::TunTest::create(false);
let router: Device<_, BencherCallbacks, dummy::TunWriter, dummy::VoidBind> =
Device::new(num_cpus::get_physical(), tun_writer);
// add peer to router
let opaque = Arc::new(TransmissionCounter::new());
let peer = router.new_peer(opaque.clone());
peer.add_keypair(dummy_keypair(true));
// add subnet to peer
let (mask, len, dst) = ("192.168.1.0", 24, "192.168.1.20");
let mask: IpAddr = mask.parse().unwrap();
peer.add_allowed_ip(mask, len);
// create "IP packet"
let dst = dst.parse().unwrap();
let src = match dst {
IpAddr::V4(_) => "127.0.0.1".parse().unwrap(),
IpAddr::V6(_) => "::1".parse().unwrap(),
};
let packet = make_packet(BYTES_PER_PACKET, src, dst, 0);
// suffix with zero and reserve capacity for tag
// (normally done to enable in-place transport message construction)
let mut msg = pad(&packet);
msg.reserve(16);
// setup profiler
#[cfg(feature = "profiler")]
profiler_start("outbound");
// repeatedly transmit 10 GB
b.iter(|| {
opaque.reset();
while opaque.sent() < BYTES_PER_ITER / packet.len() {
router
.send(msg.to_vec())
.expect("failed to crypto-route packet");
}
});
// stop profiler
#[cfg(feature = "profiler")]
profiler_stop();
}
/*
#[test]
fn bench_router_bidirectional(b: &mut Bencher) {
const MAX_SIZE_BODY: usize = 1500;
let tests = [
(
("192.168.1.0", 24, "192.168.1.20", true),
("172.133.133.133", 32, "172.133.133.133", true),
),
(
("192.168.1.0", 24, "192.168.1.20", true),
("172.133.133.133", 32, "172.133.133.133", true),
),
(
(
"2001:db8::ff00:42:8000",
113,
"2001:db8::ff00:42:ffff",
true,
),
(
"2001:db8::ff40:42:8000",
113,
"2001:db8::ff40:42:ffff",
true,
),
),
(
(
"2001:db8::ff00:42:8000",
113,
"2001:db8::ff00:42:ffff",
true,
),
(
"2001:db8::ff40:42:8000",
113,
"2001:db8::ff40:42:ffff",
true,
),
),
];
let p1 = ("192.168.1.0", 24, "192.168.1.20");
let p2 = ("172.133.133.133", 32, "172.133.133.133");
let ((bind_reader1, bind_writer1), (bind_reader2, bind_writer2)) = dummy::PairBind::pair();
let mut confirm_packet_size = SIZE_KEEPALIVE;
// create matching device
let (_fake, _, tun_writer1, _) = dummy::TunTest::create(false);
let (_fake, _, tun_writer2, _) = dummy::TunTest::create(false);
let router1: Device<_, TestCallbacks, _, _> = Device::new(1, tun_writer1);
router1.set_outbound_writer(bind_writer1);
let router2: Device<_, TestCallbacks, _, _> = Device::new(1, tun_writer2);
router2.set_outbound_writer(bind_writer2);
// prepare opaque values for tracing callbacks
let opaque1 = Opaque::new();
let opaque2 = Opaque::new();
// create peers with matching keypairs and assign subnets
let peer1 = router1.new_peer(opaque1.clone());
let peer2 = router2.new_peer(opaque2.clone());
{
let (mask, len, _ip, _okay) = p1;
let mask: IpAddr = mask.parse().unwrap();
peer1.add_allowed_ip(mask, *len);
peer1.add_keypair(dummy_keypair(false));
}
{
let (mask, len, _ip, _okay) = p2;
let mask: IpAddr = mask.parse().unwrap();
peer2.add_allowed_ip(mask, *len);
peer2.set_endpoint(dummy::UnitEndpoint::new());
}
if confirm_with_staged_packet {
// create IP packet
let (_mask, _len, ip1, _okay) = p1;
let (_mask, _len, ip2, _okay) = p2;
let msg = make_packet(
SIZE_MSG,
ip1.parse().unwrap(), // src
ip2.parse().unwrap(), // dst
0,
);
// calculate size of encapsulated IP packet
confirm_packet_size = msg.len() + SIZE_KEEPALIVE;
// stage packet for sending
router2
.send(pad(&msg))
.expect("failed to sent staged packet");
// a new key should have been requested from the handshake machine
assert_eq!(
opaque2.need_key.wait(TIMEOUT),
Some(()),
"a new key should be requested since a packet was attempted transmitted"
);
// no other events should fire
no_events!(opaque1);
no_events!(opaque2);
}
// add a keypair
assert_eq!(peer1.get_endpoint(), None, "no endpoint has yet been set");
peer2.add_keypair(dummy_keypair(true));
// this should cause a key-confirmation packet (keepalive or staged packet)
assert_eq!(
opaque2.send.wait(TIMEOUT),
Some((confirm_packet_size, true)),
"expected successful transmission of a confirmation packet"
);
// no other events should fire
no_events!(opaque1);
no_events!(opaque2);
// read confirming message received by the other end ("across the internet")
let mut buf = vec![0u8; SIZE_MSG * 2];
let (len, from) = bind_reader1.read(&mut buf).unwrap();
buf.truncate(len);
assert_eq!(
len, confirm_packet_size,
"unexpected size of confirmation message"
);
// pass to the router for processing
router1
.recv(from, buf)
.expect("failed to receive confirmation message");
// check that a receive event is fired
assert_eq!(
opaque1.recv.wait(TIMEOUT),
Some((confirm_packet_size, true)),
"we expect processing to be successful"
);
// the key is confirmed
assert_eq!(
opaque1.key_confirmed.wait(TIMEOUT),
Some(()),
"confirmation message should confirm the key"
);
// peer1 learns the endpoint
assert!(
peer1.get_endpoint().is_some(),
"peer1 should learn the endpoint of peer2 from the confirmation message (roaming)"
);
// no other events should fire
no_events!(opaque1);
no_events!(opaque2);
// now that peer1 has an endpoint
// route packets in the other direction: peer1 -> peer2
let mut sizes = vec![0, 1, 1500, MAX_SIZE_BODY];
for _ in 0..100 {
let body_size: usize = rng.gen();
let body_size = body_size % MAX_SIZE_BODY;
sizes.push(body_size);
}
for (id, body_size) in sizes.iter().enumerate() {
println!("packet: id = {}, body_size = {}", id, body_size);
// pass IP packet to router
let (_mask, _len, ip1, _okay) = p1;
let (_mask, _len, ip2, _okay) = p2;
let msg = make_packet(
*body_size,
ip2.parse().unwrap(), // src
ip1.parse().unwrap(), // dst
id as u64,
);
// calculate encrypted size
let encrypted_size = msg.len() + SIZE_KEEPALIVE;
router1
.send(pad(&msg))
.expect("we expect routing to be successful");
// encryption succeeds and the correct size is logged
assert_eq!(
opaque1.send.wait(TIMEOUT),
Some((encrypted_size, true)),
"expected send event for peer1 -> peer2 payload"
);
// otherwise no events
no_events!(opaque1);
no_events!(opaque2);
// receive ("across the internet") on the other end
let mut buf = vec![0u8; MAX_SIZE_BODY + 512];
let (len, from) = bind_reader2.read(&mut buf).unwrap();
buf.truncate(len);
router2.recv(from, buf).unwrap();
// check that decryption succeeds
assert_eq!(
opaque2.recv.wait(TIMEOUT),
Some((msg.len() + SIZE_KEEPALIVE, true)),
"decryption and routing should succeed"
);
// otherwise no events
no_events!(opaque1);
no_events!(opaque2);
}
}
#[bench]
fn bench_router_inbound(b: &mut Bencher) {
struct BencherCallbacks {}
impl Callbacks for BencherCallbacks {
type Opaque = Arc<AtomicUsize>;
fn send(
_t: &Self::Opaque,
_size: usize,
_sent: bool,
_keypair: &Arc<KeyPair>,
_counter: u64,
) {
}
fn recv(t: &Self::Opaque, size: usize, _sent: bool, _keypair: &Arc<KeyPair>) {
t.fetch_add(size, Ordering::SeqCst);
}
fn need_key(_t: &Self::Opaque) {}
fn key_confirmed(_t: &Self::Opaque) {}
}
// create device
let (_fake, _reader, tun_writer, _mtu) = dummy::TunTest::create(false);
let router: Device<_, BencherCallbacks, dummy::TunWriter, dummy::VoidBind> =
Device::new(num_cpus::get_physical(), tun_writer);
// add new peer
let opaque = Arc::new(AtomicUsize::new(0));
let peer = router.new_peer(opaque.clone());
peer.add_keypair(dummy_keypair(true));
// add subnet to peer
let (mask, len, dst) = ("192.168.1.0", 24, "192.168.1.20");
let mask: IpAddr = mask.parse().unwrap();
peer.add_allowed_ip(mask, len);
// create "IP packet"
let dst = dst.parse().unwrap();
let src = match dst {
IpAddr::V4(_) => "127.0.0.1".parse().unwrap(),
IpAddr::V6(_) => "::1".parse().unwrap(),
};
let mut msg = pad(&make_packet(1024, src, dst, 0));
msg.reserve(16);
#[cfg(feature = "profiler")]
profiler_start("outbound");
// every iteration sends 10 GB
b.iter(|| {
opaque.store(0, Ordering::SeqCst);
while opaque.load(Ordering::Acquire) < 10 * 1024 * 1024 {
router.send(msg.to_vec()).unwrap();
}
});
#[cfg(feature = "profiler")]
profiler_stop();
}
*/

View File

@@ -0,0 +1,48 @@
mod bench;
mod tests;
use super::message_data_len;
use super::SIZE_MESSAGE_PREFIX;
use super::{Callbacks, Device};
use super::{Key, KeyPair};
use super::super::dummy;
use super::super::tests::make_packet;
use std::time::Instant;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
fn pad(msg: &[u8]) -> Vec<u8> {
let mut o = vec![0; msg.len() + SIZE_MESSAGE_PREFIX];
o[SIZE_MESSAGE_PREFIX..SIZE_MESSAGE_PREFIX + msg.len()].copy_from_slice(msg);
o
}
pub fn dummy_keypair(initiator: bool) -> KeyPair {
let k1 = Key {
key: [0x53u8; 32],
id: 0x646e6573,
};
let k2 = Key {
key: [0x52u8; 32],
id: 0x76636572,
};
if initiator {
KeyPair {
birth: Instant::now(),
initiator: true,
send: k1,
recv: k2,
}
} else {
KeyPair {
birth: Instant::now(),
initiator: false,
send: k2,
recv: k1,
}
}
}

View File

@@ -1,28 +1,15 @@
use super::KeyPair;
use super::SIZE_MESSAGE_PREFIX;
use super::{Callbacks, Device};
use super::message_data_len;
use super::super::dummy;
use super::super::dummy_keypair;
use super::super::tests::make_packet;
use crate::platform::udp::Reader;
use std::net::IpAddr;
use std::ops::Deref;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use env_logger;
use num_cpus;
use rand::Rng;
use test::Bencher;
use super::*;
extern crate test;
@@ -130,67 +117,6 @@ impl Callbacks for TestCallbacks {
}
}
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
fn pad(msg: &[u8]) -> Vec<u8> {
let mut o = vec![0; msg.len() + SIZE_MESSAGE_PREFIX];
o[SIZE_MESSAGE_PREFIX..SIZE_MESSAGE_PREFIX + msg.len()].copy_from_slice(msg);
o
}
#[bench]
fn bench_outbound(b: &mut Bencher) {
struct BencherCallbacks {}
impl Callbacks for BencherCallbacks {
type Opaque = Arc<AtomicUsize>;
fn send(
t: &Self::Opaque,
size: usize,
_sent: bool,
_keypair: &Arc<KeyPair>,
_counter: u64,
) {
t.fetch_add(size, Ordering::SeqCst);
}
fn recv(_: &Self::Opaque, _size: usize, _sent: bool, _keypair: &Arc<KeyPair>) {}
fn need_key(_: &Self::Opaque) {}
fn key_confirmed(_: &Self::Opaque) {}
}
// create device
let (_fake, _reader, tun_writer, _mtu) = dummy::TunTest::create(false);
let router: Device<_, BencherCallbacks, dummy::TunWriter, dummy::VoidBind> =
Device::new(num_cpus::get(), tun_writer);
// add new peer
let opaque = Arc::new(AtomicUsize::new(0));
let peer = router.new_peer(opaque.clone());
peer.add_keypair(dummy_keypair(true));
// add subnet to peer
let (mask, len, dst) = ("192.168.1.0", 24, "192.168.1.20");
let mask: IpAddr = mask.parse().unwrap();
peer.add_allowed_ip(mask, len);
// create "IP packet"
let dst = dst.parse().unwrap();
let src = match dst {
IpAddr::V4(_) => "127.0.0.1".parse().unwrap(),
IpAddr::V6(_) => "::1".parse().unwrap(),
};
let msg = pad(&make_packet(1024, src, dst, 0));
// every iteration sends 10 GB
b.iter(|| {
opaque.store(0, Ordering::SeqCst);
while opaque.load(Ordering::Acquire) < 10 * 1024 * 1024 {
router.send(msg.to_vec()).unwrap();
}
});
}
#[test]
fn test_outbound() {
init();

View File

@@ -1,9 +1,11 @@
use std::error::Error;
use std::fmt;
use std::sync::Arc;
use super::KeyPair;
use alloc::sync::Arc;
use core::fmt;
// TODO: no_std alternatives
use std::error::Error;
pub trait Opaque: Send + Sync + 'static {}
impl<T> Opaque for T where T: Send + Sync + 'static {}

View File

@@ -1,10 +1,10 @@
use super::super::{tun, udp, Endpoint};
use super::types::Callbacks;
use super::queue::ParallelJob;
use super::receive::ReceiveJob;
use super::send::SendJob;
use super::super::{tun, udp, Endpoint};
use super::types::Callbacks;
use crossbeam_channel::Receiver;
use log;

View File

@@ -59,7 +59,14 @@ fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
/* Create and configure two matching pure instances of WireGuard
/* Create and configure
* two matching pure (no side-effects) instances of WireGuard.
*
* Test:
*
* - Handshaking completes successfully
* - All packets up to MTU are delivered
* - All packets are delivered in-order
*/
#[test]
fn test_pure_wireguard() {
@@ -137,7 +144,7 @@ fn test_pure_wireguard() {
for id in 0..num_packets {
packets.push(make_packet(
50 + 50 * id as usize, // size
50 * id as usize, // size
"192.168.1.20".parse().unwrap(), // src
"192.168.2.10".parse().unwrap(), // dst
id as u64, // prng seed
@@ -153,7 +160,6 @@ fn test_pure_wireguard() {
while let Some(p) = backup.pop() {
println!("read");
assert_eq!(
hex::encode(fake2.read()),
hex::encode(p),

View File

@@ -2,33 +2,6 @@ use clear_on_drop::clear::Clear;
use std::fmt;
use std::time::Instant;
#[cfg(test)]
pub fn dummy_keypair(initiator: bool) -> KeyPair {
let k1 = Key {
key: [0x53u8; 32],
id: 0x646e6573,
};
let k2 = Key {
key: [0x52u8; 32],
id: 0x76636572,
};
if initiator {
KeyPair {
birth: Instant::now(),
initiator: true,
send: k1,
recv: k2,
}
} else {
KeyPair {
birth: Instant::now(),
initiator: false,
send: k2,
recv: k1,
}
}
}
#[derive(Clone)]
pub struct Key {
pub key: [u8; 32],