diff --git a/Cargo.lock b/Cargo.lock index 02cfca7..f8278ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,6 +373,19 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -401,6 +414,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -768,6 +790,7 @@ dependencies = [ "blake2", "chacha20poly1305", "criterion", + "crossbeam", "dispatch", "etherparse", "hex", @@ -777,6 +800,7 @@ dependencies = [ "libc", "mock_instant", "nix", + "once_cell", "parking_lot", "rand", "rand_core", diff --git a/neptun/Cargo.toml b/neptun/Cargo.toml index 6981736..0f53577 100644 --- a/neptun/Cargo.toml +++ b/neptun/Cargo.toml @@ -12,12 +12,13 @@ edition = "2018" [features] default = [] -device = ["socket2", "thiserror"] +device = ["thiserror"] # mocks std::time::Instant with mock_instant mock-instant = ["mock_instant"] [dependencies] base64 = "0.13" +crossbeam = "0.8.4" hex = { version = "0.4" } untrusted = "0.9.0" libc = { version = "0.2", default-features = false } @@ -33,8 +34,9 @@ aead = { version = "0.5.0-pre.2", default-features = false } blake2 = { version = "0.10", default-features = false } hmac = "0.12" mock_instant = { version = "0.2", optional = true } -socket2 = { version = "0.5", features = ["all"], optional = true } +socket2 = { version = "0.5", features = ["all"] } thiserror = { version = "1", optional = true } +once_cell = "1.20.2" [target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))'.dependencies] dispatch = { git = "https://github.com/NordSecurity/rust-dispatch.git", rev = "13447cd7221a74ebcce1277ae0cfc9a421a28ec5" } diff --git a/neptun/src/device/mod.rs b/neptun/src/device/mod.rs index e6194f5..098b96f 100644 --- a/neptun/src/device/mod.rs +++ b/neptun/src/device/mod.rs @@ -36,17 +36,20 @@ use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; +use std::time::Duration; use crate::noise::errors::WireGuardError; use crate::noise::handshake::parse_handshake_anon; use crate::noise::rate_limiter::RateLimiter; +use crate::noise::ring_buffers::{EncryptionTaskData, RB_SIZE, TX_RING_BUFFER}; use crate::noise::{Packet, Tunn, TunnResult}; use crate::x25519; use allowed_ips::AllowedIps; +use crossbeam::channel::{Receiver, Sender}; use peer::{AllowedIP, Peer}; use poll::{EventPoll, EventRef, WaitResult}; use rand_core::{OsRng, RngCore}; -use socket2::{Domain, Protocol, Type}; +use socket2::{Domain, Protocol, Socket, Type}; use tun::TunSocket; use dev_lock::{Lock, LockReadGuard}; @@ -148,8 +151,8 @@ pub struct Device { iface: Arc, closed: bool, - udp4: Option, - udp6: Option, + udp4: Option>, + udp6: Option>, yield_notice: Option, exit_notice: Option, @@ -166,6 +169,12 @@ pub struct Device { mtu: AtomicUsize, rate_limiter: Option>, + + close_network_chan_tx: Sender<()>, + close_network_chan_rx: Receiver<()>, + + network_rx: Receiver<&'static EncryptionTaskData>, + network_tx: Sender<&'static EncryptionTaskData>, } struct ThreadData { @@ -381,7 +390,7 @@ impl Device { if let Some(peer) = self.peers.remove(pub_key) { // Found a peer to remove, now purge all references to it: { - peer.shutdown_endpoint(); // close open udp socket and free the closure + Peer::shutdown_endpoint(peer.endpoint_ref()); // close open udp socket and free the closure self.peers_by_idx.remove(&peer.index()); } self.peers_by_ip @@ -503,6 +512,9 @@ impl Device { let iface = Arc::new(tun.set_non_blocking()?); let mtu = iface.mtu()?; + let (network_tx, network_rx) = crossbeam::channel::bounded(RB_SIZE); + let (close_network_chan_tx, close_network_chan_rx) = crossbeam::channel::bounded(1); + let mut device = Device { queue: Arc::new(poll), iface, @@ -522,6 +534,10 @@ impl Device { cleanup_paths: Default::default(), mtu: AtomicUsize::new(mtu), rate_limiter: None, + network_tx: network_tx.clone(), + network_rx, + close_network_chan_tx, + close_network_chan_rx, #[cfg(not(target_os = "linux"))] update_seq: 0, }; @@ -549,6 +565,8 @@ impl Device { // Binds the network facing interfaces // First close any existing open socket, and remove them from the event loop if let Some(s) = self.udp4.take() { + // Need to handle this error + let _ = self.close_network_chan_tx.send(()); unsafe { // This is safe because the event loop is not running yet self.queue.clear_event_by_fd(s.as_raw_fd()) @@ -560,7 +578,7 @@ impl Device { } for peer in self.peers.values() { - peer.shutdown_endpoint(); + Peer::shutdown_endpoint(peer.endpoint_ref()); } // Then open new sockets and bind to the port @@ -583,8 +601,16 @@ impl Device { self.register_udp_handler(udp_sock4.try_clone().unwrap())?; self.register_udp_handler(udp_sock6.try_clone().unwrap())?; - self.udp4 = Some(udp_sock4); - self.udp6 = Some(udp_sock6); + + let udp4 = Arc::new(udp_sock4); + let udp6 = Arc::new(udp_sock6); + self.udp4 = Some(udp4.clone()); + self.udp6 = Some(udp6.clone()); + + // Send to network in a seperate thread + let rx_clone = self.network_rx.clone(); + let close_chan_clone = self.close_network_chan_rx.clone(); + thread::spawn(move || send_to_network(rx_clone, close_chan_clone, udp4, udp6)); self.listen_port = port; @@ -711,7 +737,7 @@ impl Device { match res { TunnResult::Done => {} TunnResult::Err(WireGuardError::ConnectionExpired) => { - peer.shutdown_endpoint(); // close open udp socket + Peer::shutdown_endpoint(peer.endpoint_ref()); // close open udp socket } TunnResult::Err(e) => tracing::error!(message = "Timer error", error = ?e), TunnResult::WriteToNetwork(packet) => { @@ -753,7 +779,7 @@ impl Device { let endpoint = peer.endpoint(); if endpoint.conn.is_some() { drop(endpoint); - peer.shutdown_endpoint(); + Peer::shutdown_endpoint(peer.endpoint_ref()); } } } @@ -1026,105 +1052,69 @@ impl Device { // * Send encapsulated packet to the peer's endpoint let mtu = d.mtu.load(Ordering::Relaxed); - let udp4 = d.udp4.as_ref().expect("Not connected"); - let udp6 = d.udp6.as_ref().expect("Not connected"); - let peers = &d.peers_by_ip; for _ in 0..MAX_ITR { - let src = match iface.read(&mut t.src_buf[..mtu]) { - Ok(src) => src, - Err(Error::IfaceRead(e)) => { - let ek = e.kind(); - if ek == io::ErrorKind::Interrupted || ek == io::ErrorKind::WouldBlock { - break; + let element = unsafe { TX_RING_BUFFER.get_next() }; + if element.is_element_free.load(Ordering::Relaxed) { + let len = match iface.read(&mut element.data[16..mtu + 16]) { + Ok(src) => src.len(), + Err(Error::IfaceRead(e)) => { + let ek = e.kind(); + if ek == io::ErrorKind::Interrupted + || ek == io::ErrorKind::WouldBlock + { + break; + } + tracing::error!( + message="Fatal read error on tun interface: errno", error=?e + ); + return Action::Exit; } - tracing::error!( - message="Fatal read error on tun interface: errno", error=?e - ); - return Action::Exit; - } - Err(e) => { - tracing::error!( - message="Unexpected error on tun interface", error=?e - ); - return Action::Exit; - } - }; - - let dst_addr = match Tunn::dst_address(src) { - Some(addr) => addr, - None => continue, - }; + Err(e) => { + tracing::error!( + message="Unexpected error on tun interface", error=?e + ); + return Action::Exit; + } + }; - let peer = match peers.find(dst_addr) { - Some(peer) => peer, - None => continue, - }; + let dst_addr = match Tunn::dst_address(&element.data[16..len + 16]) { + Some(addr) => addr, + None => continue, + }; + let peer = match peers.find(dst_addr) { + Some(peer) => peer, + None => continue, + }; - if let Some(callback) = &d.config.firewall_process_outbound_callback { - if !callback(&peer.public_key.0, src) { - continue; + if let Some(callback) = &d.config.firewall_process_outbound_callback { + if !callback(&peer.public_key.0, &element.data[16..len + 16]) { + continue; + } } - } - let res = { - let mut tun = peer.tunnel.lock(); - tun.encapsulate(src, &mut t.dst_buf[..]) - }; - match res { - TunnResult::Done => {} - TunnResult::Err(e) => { - tracing::error!(message = "Encapsulate error", - error = ?e, - public_key = peer.public_key.1) - } - TunnResult::WriteToNetwork(packet) => { - let endpoint = peer.endpoint(); - if let Some(conn) = endpoint.conn.as_ref() { - // Prefer to send using the connected socket - if let Err(err) = conn.send(packet) { - tracing::debug!(message = "Failed to send packet with the connected socket", error = ?err); - drop(endpoint); - peer.shutdown_endpoint(); - } else { - tracing::trace!( - "Pkt -> ConnSock ({:?}), len: {}, dst_addr: {}", - endpoint.addr, - packet.len(), - dst_addr - ); - } - } else if let Some(addr @ SocketAddr::V4(_)) = endpoint.addr { - if let Err(err) = udp4.send_to(packet, &addr.into()) { - tracing::warn!(message = "Failed to write packet to network v4", error = ?err, dst = ?addr); - } else { - tracing::trace!( - message = "Writing packet to network v4", - interface = ?t.iface.name(), - packet_length = packet.len(), - src_addr = ?addr, - public_key = peer.public_key.1 - ); - } - } else if let Some(addr @ SocketAddr::V6(_)) = endpoint.addr { - if let Err(err) = udp6.send_to(packet, &addr.into()) { - tracing::warn!(message = "Failed to write packet to network v6", error = ?err, dst = ?addr); - } else { - tracing::trace!( - message = "Writing packet to network v6", - interface = ?t.iface.name(), - packet_length = packet.len(), - src_addr = ?addr, - public_key = peer.public_key.1 - ); - } - } else { - tracing::error!("No endpoint"); + let res = { + let mut tun = peer.tunnel.lock(); + tun.encapsulate_in_place(len, &mut element.data[..]) + }; + + match res { + TunnResult::Done => {} + TunnResult::Err(e) => { + tracing::error!(message = "Encapsulate error", + error = ?e, + public_key = peer.public_key.1) } - } - _ => panic!("Unexpected result from encapsulate"), - }; + TunnResult::WriteToNetwork(packet) => { + element.endpoint = peer.endpoint_ref(); + element.buf_len = packet.len(); + element.is_element_free.store(false, Ordering::Relaxed); + let _ = d.network_tx.send(element); + } + _ => panic!("Unexpected result from encapsulate"), + }; + } } Action::Continue }), @@ -1137,6 +1127,67 @@ impl Device { } } +fn send_to_network( + network_rx: Receiver<&EncryptionTaskData>, + close_chan: Receiver<()>, + udp4: Arc, + udp6: Arc, +) { + loop { + crossbeam::channel::select! { + recv(network_rx) -> m => { + if let Ok(msg) = m { + let mut endpoint = msg.endpoint.write(); + let packet = &msg.data.as_slice()[..msg.buf_len]; + if let Some(conn) = endpoint.conn.as_mut() { + // Prefer to send using the connected socket + if let Err(err) = conn.send(packet) { + tracing::debug!(message = "Failed to send packet with the connected socket", error = ?err); + drop(endpoint); + Peer::shutdown_endpoint(msg.endpoint.clone()); + } else { + tracing::trace!( + "Pkt -> ConnSock ({:?}), len: {}", + endpoint.addr, + packet.len(), + ); + } + } else if let Some(addr @ SocketAddr::V4(_)) = endpoint.addr { + let _: Result<_, _> = udp4.send_to(packet, &addr.into()); + // if let Err(err) = udp4.send_to(packet, &addr.into()) { + // tracing::warn!(message = "Failed to write packet to network v4", error = ?err, dst = ?addr); + // } else { + // tracing::trace!( + // message = "Writing packet to network v4", + // packet_length = packet.len(), + // src_addr = ?addr, + // ); + // } + } else if let Some(addr @ SocketAddr::V6(_)) = endpoint.addr { + let _: Result<_, _> = udp6.send_to(packet, &addr.into()); + // if let Err(err) = udp6.send_to(packet, &addr.into()) { + // tracing::warn!(message = "Failed to write packet to network v6", error = ?err, dst = ?addr); + // } else { + // tracing::trace!( + // message = "Writing packet to network v6", + // packet_length = packet.len(), + // src_addr = ?addr, + // ); + // } + } else { + tracing::error!("No endpoint"); + } + msg.is_element_free.store(true, Ordering::Relaxed); + } + } + recv(close_chan) -> _n => { + break; + } + default(Duration::from_millis(10)) => (), + } + } +} + /// A basic linear-feedback shift register implemented as xorshift, used to /// distribute peer indexes across the 24-bit address space reserved for peer /// identification. diff --git a/neptun/src/device/peer.rs b/neptun/src/device/peer.rs index 24c58de..ce3f3cf 100644 --- a/neptun/src/device/peer.rs +++ b/neptun/src/device/peer.rs @@ -10,16 +10,10 @@ use std::str::FromStr; use std::sync::Arc; use crate::device::{AllowedIps, Error, MakeExternalNeptun}; -use crate::noise::Tunn; +use crate::noise::{Endpoint, Tunn}; use std::os::fd::AsRawFd; -#[derive(Default, Debug)] -pub struct Endpoint { - pub addr: Option, - pub conn: Option, -} - pub struct Peer { /// The associated tunnel struct pub(crate) tunnel: Mutex, @@ -27,7 +21,7 @@ pub struct Peer { pub(crate) public_key: ([u8; 32], String), /// The index the tunnel uses index: u32, - endpoint: RwLock, + endpoint: Arc>, allowed_ips: RwLock>, preshared_key: RwLock>, protect: Arc, @@ -77,10 +71,10 @@ impl Peer { tunnel: Mutex::new(tunnel), public_key: (pub_key.to_bytes(), public_key_hex), index, - endpoint: RwLock::new(Endpoint { + endpoint: Arc::new(RwLock::new(Endpoint { addr: endpoint, conn: None, - }), + })), allowed_ips: RwLock::new(allowed_ips.iter().map(|ip| (ip, ())).collect()), preshared_key: RwLock::new(preshared_key), protect, @@ -91,8 +85,12 @@ impl Peer { self.endpoint.read() } - pub fn shutdown_endpoint(&self) { - if let Some(conn) = self.endpoint.write().conn.take() { + pub(crate) fn endpoint_ref(&self) -> Arc> { + self.endpoint.clone() + } + + pub fn shutdown_endpoint(endpoint: Arc>) { + if let Some(conn) = endpoint.write().conn.take() { tracing::info!("Disconnecting from endpoint"); conn.shutdown(Shutdown::Both).unwrap(); } diff --git a/neptun/src/noise/mod.rs b/neptun/src/noise/mod.rs index 8517ecf..75ac9e5 100644 --- a/neptun/src/noise/mod.rs +++ b/neptun/src/noise/mod.rs @@ -5,6 +5,7 @@ pub mod errors; pub mod handshake; pub mod rate_limiter; +pub mod ring_buffers; pub mod safe_duration; #[cfg(test)] @@ -21,7 +22,7 @@ use crate::x25519; use std::collections::VecDeque; use std::convert::{TryFrom, TryInto}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use std::time::Duration; @@ -46,6 +47,12 @@ const MAX_QUEUE_DEPTH: usize = 256; /// number of sessions in the ring, better keep a PoT const N_SESSIONS: usize = 8; +#[derive(Default, Debug)] +pub struct Endpoint { + pub addr: Option, + pub conn: Option, +} + #[derive(Debug)] pub enum TunnResult<'a> { Done, @@ -61,6 +68,21 @@ impl<'a> From for TunnResult<'a> { } } +#[derive(Debug)] +pub enum NeptunResult { + Done, + Err(WireGuardError), + WriteToNetwork(usize), + WriteToTunnelV4(usize, Ipv4Addr), + WriteToTunnelV6(usize, Ipv6Addr), +} + +impl From for NeptunResult { + fn from(err: WireGuardError) -> NeptunResult { + NeptunResult::Err(err) + } +} + /// Tunnel represents a point-to-point WireGuard connection pub struct Tunn { /// The handshake currently in progress @@ -272,24 +294,35 @@ impl Tunn { /// Panics if dst buffer is too small. /// Size of dst should be at least src.len() + 32, and no less than 148 bytes. pub fn encapsulate<'a>(&mut self, src: &[u8], dst: &'a mut [u8]) -> TunnResult<'a> { + dst[16..src.len() + 16].copy_from_slice(src); + self.encapsulate_in_place(src.len(), dst) + } + + pub fn encapsulate_in_place<'a>( + &mut self, + src_len: usize, + dst: &'a mut [u8], + ) -> TunnResult<'a> { let current = self.current; if let Some(ref session) = self.sessions[current % N_SESSIONS] { // Send the packet using an established session - let packet = session.format_packet_data(src, dst); + let packet = session.format_packet_data(src_len, dst); + + // Send the notification on the channel to encrypt the packet self.timer_tick(TimerName::TimeLastPacketSent); // Exclude Keepalive packets from timer update. - if !src.is_empty() { + if src_len.ne(&0) { self.timer_tick(TimerName::TimeLastDataPacketSent); } self.tx_bytes += packet.len(); return TunnResult::WriteToNetwork(packet); } - if !src.is_empty() { + if src_len.ne(&0) { // If there is no session, queue the packet for future retry, // except if it's keepalive packet, new keepalive packets will be sent when session is created. // This prevents double keepalive packets on initiation - self.queue_packet(src); + self.queue_packet(&dst[16..src_len + 16]); } // Initiate a new handshake if none is in progress @@ -393,7 +426,7 @@ impl Tunn { // Increase the rx_bytes accordingly self.rx_bytes += HANDSHAKE_RESP_SZ; - let keepalive_packet = session.format_packet_data(&[], dst); + let keepalive_packet = { session.format_packet_data(0, dst) }; // Store new session in ring buffer let l_idx = session.local_index(); let index = l_idx % N_SESSIONS; @@ -559,6 +592,7 @@ impl Tunn { /// Get a packet from the queue, and try to encapsulate it fn send_queued_packet<'a>(&mut self, dst: &'a mut [u8]) -> TunnResult<'a> { + // TODO: Fix this with encapsulate without peer ??? if let Some(packet) = self.dequeue_packet() { match self.encapsulate(&packet, dst) { TunnResult::Err(_) => { @@ -638,6 +672,7 @@ impl Tunn { #[cfg(test)] mod tests { + #[cfg(feature = "mock-instant")] use crate::noise::timers::{REKEY_AFTER_TIME, REKEY_TIMEOUT}; diff --git a/neptun/src/noise/ring_buffers.rs b/neptun/src/noise/ring_buffers.rs new file mode 100644 index 0000000..a072bac --- /dev/null +++ b/neptun/src/noise/ring_buffers.rs @@ -0,0 +1,50 @@ +use super::Endpoint; +use once_cell::sync::Lazy; +use parking_lot::Mutex; +use std::sync::{atomic::AtomicBool, Arc}; +const UDP_SIZE: usize = (1 << 12) - 1; + +pub const RB_SIZE: usize = 20; + +pub struct RingBuffer { + pub ring_buffer: Vec, + iter: Mutex, +} + +impl RingBuffer { + // Returns the next element in ring buffer + // and moves the ring buffer iterator forward + pub fn get_next(&mut self) -> &mut T { + let mut idx = self.iter.lock(); + if *idx == RB_SIZE { + // Reset the write iterator + *idx = 0; + } + let element = &mut self.ring_buffer[*idx]; + *idx += 1; + element + } +} + +pub struct EncryptionTaskData { + pub data: [u8; UDP_SIZE], + pub buf_len: usize, + pub endpoint: Arc>, + pub is_element_free: AtomicBool, +} + +pub static mut TX_RING_BUFFER: Lazy> = Lazy::new(|| { + let mut deque = Vec::with_capacity(RB_SIZE); + for _ in 0..RB_SIZE { + deque.push(EncryptionTaskData { + data: [0; UDP_SIZE], + buf_len: 0, + endpoint: Arc::default(), + is_element_free: AtomicBool::new(true), + }); + } + RingBuffer { + ring_buffer: deque, + iter: Mutex::new(0), + } +}); diff --git a/neptun/src/noise/session.rs b/neptun/src/noise/session.rs index 3aa6408..f824de9 100644 --- a/neptun/src/noise/session.rs +++ b/neptun/src/noise/session.rs @@ -30,7 +30,7 @@ impl std::fmt::Debug for Session { /// Where encrypted data resides in a data packet const DATA_OFFSET: usize = 16; /// The overhead of the AEAD -const AEAD_SIZE: usize = 16; +pub const AEAD_SIZE: usize = 16; // Receiving buffer constants const WORD_SIZE: u64 = 64; @@ -194,8 +194,8 @@ impl Session { /// src - an IP packet from the interface /// dst - pre-allocated space to hold the encapsulating UDP packet to send over the network /// returns the size of the formatted packet - pub(super) fn format_packet_data<'a>(&self, src: &[u8], dst: &'a mut [u8]) -> &'a mut [u8] { - if dst.len() < src.len() + super::DATA_OVERHEAD_SZ { + pub(super) fn format_packet_data<'a>(&self, src_len: usize, dst: &'a mut [u8]) -> &'a mut [u8] { + if dst.len() < src_len + super::DATA_OVERHEAD_SZ { panic!("The destination buffer is too small"); } @@ -213,16 +213,16 @@ impl Session { let n = { let mut nonce = [0u8; 12]; nonce[4..12].copy_from_slice(&sending_key_counter.to_le_bytes()); - data[..src.len()].copy_from_slice(src); + // data[..src.len()].copy_from_slice(src); self.sender .seal_in_place_separate_tag( Nonce::assume_unique_for_key(nonce), Aad::from(&[]), - &mut data[..src.len()], + &mut data[..src_len], ) .map(|tag| { - data[src.len()..src.len() + AEAD_SIZE].copy_from_slice(tag.as_ref()); - src.len() + AEAD_SIZE + data[src_len..src_len + AEAD_SIZE].copy_from_slice(tag.as_ref()); + src_len + AEAD_SIZE }) .unwrap() }; diff --git a/neptun/src/noise/timers.rs b/neptun/src/noise/timers.rs index 07b4bfd..fe3181d 100644 --- a/neptun/src/noise/timers.rs +++ b/neptun/src/noise/timers.rs @@ -42,7 +42,7 @@ pub(crate) const REKEY_TIMEOUT: Duration = Duration::from_secs(5); const KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(10); const COOKIE_EXPIRATION_TIME: Duration = Duration::from_secs(120); -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum TimerName { /// Current time, updated each call to `update_timers` TimeCurrent, diff --git a/xtask/perf/left.sh b/xtask/perf/left.sh index d39e2f3..e712611 100755 --- a/xtask/perf/left.sh +++ b/xtask/perf/left.sh @@ -30,18 +30,51 @@ wg set wg2 \ ip address add dev wg2 10.0.2.1/24 ip link set up dev wg2 -echo -echo "Raw network:" -iperf3 -i 10 -t 10 --bidir -c 176.0.0.3 +# echo +# echo "Raw network:" +# iperf3 -i 10 -t 10 --bidir -c 176.0.0.3 -echo -echo "Wireguard-go:" -iperf3 -i 60 -t 120 --bidir -c 10.0.0.2 +# echo +# echo "Wireguard-go:" +# iperf3 -i 60 -t 120 --bidir -c 10.0.0.2 -echo -echo "Base NepTUN:" -iperf3 -i 60 -t 120 --bidir -c 10.0.1.2 +# echo +# echo "TCP bidirectional tests" + +# echo +# echo "Base NepTUN:" +# iperf3 -i 60 -t 120 --bidir -c 10.0.1.2 + +# echo +# echo "Current NepTUN:" +# iperf3 -i 60 -t 120 --bidir -c 10.0.2.2 echo -echo "Current NepTUN:" -iperf3 -i 60 -t 120 --bidir -c 10.0.2.2 +echo "UDP unidirectional tests" + +bitrates=(40M 100M 200M 500M 600M 700M 800M 1000M 1200M 1500M 1800M 2000M 2200M 2300M 2400M 2500M 2700M 3000M 3500M 4000M 4500M 5000M) + +for bitrate in "${bitrates[@]}" +do + echo + echo "Running test for bitrate: $bitrate" + # Base NepTUN + base_cmd=$(iperf3 -i 60 -t 120 -u -b "$bitrate" -c 10.0.1.2 | awk '/receiver/') + base_output="$base_cmd" + base_total_datagrams=$(echo "$base_output" | awk '{print $11}' | awk -F '/' '{print $2}') + base_lost_percentage=$(echo "$base_output" | awk '{print $12}') + base_bitrate=$(echo "$base_output" | awk '{print $7 " " $8}') + + # Current NepTUN + current_cmd=$(iperf3 -i 60 -t 120 -u -b "$bitrate" -c 10.0.2.2 | awk '/receiver/') + current_output="$current_cmd" + current_total_datagrams=$(echo "$current_output" | awk '{print $11}' | awk -F '/' '{print $2}') + current_lost_percentage=$(echo "$current_output" | awk '{print $12}') + current_bitrate=$(echo "$current_output" | awk '{print $7 " " $8}') + + # Print results + echo "Connection | Total Datagrams | Lost (%) | Received Bitrate" + echo "Base NepTUN | $base_total_datagrams | $base_lost_percentage | $base_bitrate " + echo "Current NepTUN | $current_total_datagrams | $current_lost_percentage | $current_bitrate " + sleep 1 +done