From 07edf9d2da7c3ab0990fd237df2c05eca51f00a7 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 17:00:33 -0400 Subject: [PATCH 001/165] Remove unnecessary (and expensive) logic to stack packets together --- src/main.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index 12c520b..6158c6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -171,10 +171,6 @@ async fn handle_tcp_proxy_connection( .expect("failed to wait for virtual client to be ready"); trace!("[{}] Virtual client is ready to send data", virtual_port); - // Data that has been received from the client, but hasn't been flushed to the - // virtual client just yet. - let mut unflushed_data = Vec::with_capacity(MAX_PACKET); - loop { if abort.load(Ordering::Relaxed) { break; @@ -193,21 +189,14 @@ async fn handle_tcp_proxy_connection( "[{}] Read {} bytes of TCP data from real client", virtual_port, size ); - unflushed_data.extend_from_slice(data); + if let Err(e) = data_to_virtual_server_tx.send(data.to_vec()).await { + error!( + "[{}] Failed to dispatch data to virtual interface: {:?}", + virtual_port, e + ); + } } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - trace!("[{}] Real client blocked; have {} bytes to flush", virtual_port, unflushed_data.len()); - if !unflushed_data.is_empty() { - let data = unflushed_data; - // Reset unflushed data - unflushed_data = Vec::with_capacity(MAX_PACKET); - if let Err(e) = data_to_virtual_server_tx.send(data).await { - error!( - "[{}] Failed to dispatch data to virtual interface: {:?}", - virtual_port, e - ); - } - } continue; } Err(e) => { @@ -368,6 +357,11 @@ async fn virtual_tcp_interface( if client_socket.can_recv() { match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { Ok(data) => { + trace!( + "[{}] Virtual client received {} bytes of data", + virtual_port, + data.len() + ); // Send it to the real client if let Err(e) = data_to_real_client_tx.send(data).await { error!("[{}] Failed to dispatch data from virtual client to real client: {:?}", virtual_port, e); From b6739a5066883f98e5200ea6348a6572990bafbc Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 17:04:00 -0400 Subject: [PATCH 002/165] release: v0.1.7 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b98fbd2..7bd7c43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.1.6" +version = "0.1.7" dependencies = [ "anyhow", "boringtun", diff --git a/Cargo.toml b/Cargo.toml index 1a54db9..bafce0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.1.6" +version = "0.1.7" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From cfdbdc8f51327e7721e6ceac9630032cd900fecb Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 19:16:10 -0400 Subject: [PATCH 003/165] Remove broadcasting logic to fix simultaneous connection issues. --- README.md | 2 +- src/main.rs | 23 +++--- src/port_pool.rs | 2 +- src/virtual_device.rs | 20 +++--- src/wg.rs | 162 ++++++++++++++++-------------------------- 5 files changed, 86 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index 20f32e7..8b7681e 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ forward it to the server's port, which handles the TCP segment. The server respo the peer's local WireGuard interface, gets encrypted, forwarded to the WireGuard endpoint, and then finally back to onetun's UDP port. When onetun receives an encrypted packet from the WireGuard endpoint, it decrypts it using boringtun. -The resulting IP packet is broadcasted to all virtual interfaces running inside onetun; once the corresponding +The resulting IP packet is dispatched to the corresponding virtual interface running inside onetun; once the corresponding interface is matched, the IP packet is read and unpacked, and the virtual client's TCP state is updated. Whenever data is sent by the real client, it is simply "sent" by the virtual client, which kicks off the whole IP encapsulation diff --git a/src/main.rs b/src/main.rs index 6158c6f..7ae3e0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ async fn main() -> anyhow::Result<()> { init_logger(&config)?; let port_pool = Arc::new(PortPool::new()); - let wg = WireGuardTunnel::new(&config, port_pool.clone()) + let wg = WireGuardTunnel::new(&config) .await .with_context(|| "Failed to initialize WireGuard tunnel")?; let wg = Arc::new(wg); @@ -47,12 +47,6 @@ async fn main() -> anyhow::Result<()> { tokio::spawn(async move { wg.consume_task().await }); } - { - // Start IP broadcast drain task for WireGuard - let wg = wg.clone(); - tokio::spawn(async move { wg.broadcast_drain_task().await }); - } - info!( "Tunnelling [{}]->[{}] (via [{}] as peer {})", &config.source_addr, &config.dest_addr, &config.endpoint_addr, &config.source_peer_ip @@ -106,9 +100,14 @@ async fn tcp_proxy_server( tokio::spawn(async move { let port_pool = Arc::clone(&port_pool); - let result = - handle_tcp_proxy_connection(socket, virtual_port, source_peer_ip, dest_addr, wg) - .await; + let result = handle_tcp_proxy_connection( + socket, + virtual_port, + source_peer_ip, + dest_addr, + wg.clone(), + ) + .await; if let Err(e) = result { error!( @@ -120,6 +119,7 @@ async fn tcp_proxy_server( } // Release port when connection drops + wg.release_virtual_interface(virtual_port); port_pool.release(virtual_port); }); } @@ -270,7 +270,8 @@ async fn virtual_tcp_interface( // Consumer for IP packets to send through the virtual interface // Initialize the interface - let device = VirtualIpDevice::new(wg); + let device = VirtualIpDevice::new(virtual_port, wg) + .with_context(|| "Failed to initialize VirtualIpDevice")?; let mut virtual_interface = InterfaceBuilder::new(device) .ip_addrs([ // Interface handles IP packets for the sender and recipient diff --git a/src/port_pool.rs b/src/port_pool.rs index a932b7c..7bff712 100644 --- a/src/port_pool.rs +++ b/src/port_pool.rs @@ -13,7 +13,7 @@ const PORT_RANGE: Range = MIN_PORT..MAX_PORT; pub struct PortPool { /// Remaining ports inner: lockfree::queue::Queue, - /// Ports in use + /// Ports in use, with their associated IP channel sender. taken: lockfree::set::Set, } diff --git a/src/virtual_device.rs b/src/virtual_device.rs index ad60b63..3ca2416 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -1,26 +1,26 @@ use crate::wg::WireGuardTunnel; +use anyhow::Context; use smoltcp::phy::{Device, DeviceCapabilities, Medium}; use smoltcp::time::Instant; use std::sync::Arc; /// A virtual device that processes IP packets. IP packets received from the WireGuard endpoint -/// are made available to this device using a broadcast channel receiver. IP packets sent from this device +/// are made available to this device using a channel receiver. IP packets sent from this device /// are asynchronously sent out to the WireGuard tunnel. pub struct VirtualIpDevice { /// Tunnel to send IP packets to. wg: Arc, - /// Broadcast channel receiver for received IP packets. - ip_broadcast_rx: tokio::sync::broadcast::Receiver>, + /// Channel receiver for received IP packets. + ip_dispatch_rx: tokio::sync::mpsc::Receiver>, } impl VirtualIpDevice { - pub fn new(wg: Arc) -> Self { - let ip_broadcast_rx = wg.subscribe(); + pub fn new(virtual_port: u16, wg: Arc) -> anyhow::Result { + let ip_dispatch_rx = wg + .register_virtual_interface(virtual_port) + .with_context(|| "Failed to register IP dispatch for virtual interface")?; - Self { - wg, - ip_broadcast_rx, - } + Ok(Self { wg, ip_dispatch_rx }) } } @@ -29,7 +29,7 @@ impl<'a> Device<'a> for VirtualIpDevice { type TxToken = TxToken; fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { - match self.ip_broadcast_rx.try_recv() { + match self.ip_dispatch_rx.try_recv() { Ok(buffer) => Some(( Self::RxToken { buffer }, Self::TxToken { diff --git a/src/wg.rs b/src/wg.rs index 451afca..f748794 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -1,10 +1,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::sync::Arc; use std::time::Duration; use anyhow::Context; use boringtun::noise::{Tunn, TunnResult}; -use futures::lock::Mutex; use log::Level; use smoltcp::phy::ChecksumCapabilities; use smoltcp::wire::{ @@ -12,14 +10,12 @@ use smoltcp::wire::{ TcpPacket, TcpRepr, TcpSeqNumber, }; use tokio::net::UdpSocket; -use tokio::sync::broadcast::error::RecvError; use crate::config::Config; -use crate::port_pool::PortPool; use crate::MAX_PACKET; -/// The capacity of the broadcast channel for received IP packets. -const BROADCAST_CAPACITY: usize = 1_000; +/// The capacity of the channel for received IP packets. +const DISPATCH_CAPACITY: usize = 1_000; /// A WireGuard tunnel. Encapsulates and decapsulates IP packets /// to be sent to and received from a remote UDP endpoint. @@ -32,34 +28,27 @@ pub struct WireGuardTunnel { udp: UdpSocket, /// The address of the public WireGuard endpoint (UDP). endpoint: SocketAddr, - /// Broadcast sender for received IP packets. - ip_broadcast_tx: tokio::sync::broadcast::Sender>, - /// Sink so that the broadcaster doesn't close. A repeating task should drain this as much as possible. - ip_broadcast_rx_sink: Mutex>>, - /// Port pool. - port_pool: Arc, + /// Maps virtual ports to the corresponding IP packet dispatcher. + virtual_port_ip_tx: lockfree::map::Map>>, } impl WireGuardTunnel { /// Initialize a new WireGuard tunnel. - pub async fn new(config: &Config, port_pool: Arc) -> anyhow::Result { + pub async fn new(config: &Config) -> anyhow::Result { let source_peer_ip = config.source_peer_ip; let peer = Self::create_tunnel(config)?; let udp = UdpSocket::bind("0.0.0.0:0") .await .with_context(|| "Failed to create UDP socket for WireGuard connection")?; let endpoint = config.endpoint_addr; - let (ip_broadcast_tx, ip_broadcast_rx_sink) = - tokio::sync::broadcast::channel(BROADCAST_CAPACITY); + let virtual_port_ip_tx = lockfree::map::Map::new(); Ok(Self { source_peer_ip, peer, udp, endpoint, - ip_broadcast_tx, - ip_broadcast_rx_sink: Mutex::new(ip_broadcast_rx_sink), - port_pool, + virtual_port_ip_tx, }) } @@ -94,9 +83,24 @@ impl WireGuardTunnel { Ok(()) } - /// Create a new receiver for broadcasted IP packets, received from the WireGuard endpoint. - pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver> { - self.ip_broadcast_tx.subscribe() + /// Register a virtual interface (using its assigned virtual port) with the given IP packet `Sender`. + pub fn register_virtual_interface( + &self, + virtual_port: u16, + ) -> anyhow::Result>> { + let existing = self.virtual_port_ip_tx.get(&virtual_port); + if existing.is_some() { + Err(anyhow::anyhow!("Cannot register virtual interface with virtual port {} because it is already registered", virtual_port)) + } else { + let (sender, receiver) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); + self.virtual_port_ip_tx.insert(virtual_port, sender); + Ok(receiver) + } + } + + /// Releases the virtual interface from IP dispatch. + pub fn release_virtual_interface(&self, virtual_port: u16) { + self.virtual_port_ip_tx.remove(&virtual_port); } /// WireGuard Routine task. Handles Handshake, keep-alive, etc. @@ -139,7 +143,7 @@ impl WireGuardTunnel { } /// WireGuard consumption task. Receives encrypted packets from the WireGuard endpoint, - /// decapsulates them, and broadcasts newly received IP packets. + /// decapsulates them, and dispatches newly received IP packets. pub async fn consume_task(&self) -> ! { trace!("Starting WireGuard consumption task"); @@ -195,30 +199,33 @@ impl WireGuardTunnel { trace_ip_packet("Received IP packet", packet); match self.route_ip_packet(packet) { - RouteResult::Broadcast => { - // Broadcast IP packet - if self.ip_broadcast_tx.receiver_count() > 1 { - match self.ip_broadcast_tx.send(packet.to_vec()) { - Ok(n) => { + RouteResult::Dispatch(port) => { + let sender = self.virtual_port_ip_tx.get(&port); + if let Some(sender_guard) = sender { + let sender = sender_guard.val(); + match sender.send(packet.to_vec()).await { + Ok(_) => { trace!( - "Broadcasted received IP packet to {} virtual interfaces", - n - 1 + "Dispatched received IP packet to virtual port {}", + port ); } Err(e) => { error!( - "Failed to broadcast received IP packet to recipients: {}", - e + "Failed to dispatch received IP packet to virtual port {}: {}", + port, e ); } } + } else { + warn!("[{}] Race condition: failed to get virtual port sender after it was dispatched", port); } } - RouteResult::TcpReset(packet) => { + RouteResult::TcpReset => { trace!("Resetting dead TCP connection after packet from WireGuard endpoint"); - self.send_ip_packet(&packet) - .await - .unwrap_or_else(|e| error!("Failed to sent TCP reset: {:?}", e)); + self.route_tcp_sink(packet).await.unwrap_or_else(|e| { + error!("Failed to send TCP reset to sink: {:?}", e) + }); } RouteResult::Drop => { trace!("Dropped incoming IP packet from WireGuard endpoint"); @@ -230,33 +237,6 @@ impl WireGuardTunnel { } } - /// A repeating task that drains the default IP broadcast channel receiver. - /// It is necessary to keep this receiver alive to prevent the overall channel from closing, - /// so draining its backlog regularly is required to avoid memory leaks. - pub async fn broadcast_drain_task(&self) { - trace!("Starting IP broadcast sink drain task"); - - loop { - let mut sink = self.ip_broadcast_rx_sink.lock().await; - match sink.recv().await { - Ok(_) => { - trace!("Drained a packet from IP broadcast sink"); - } - Err(e) => match e { - RecvError::Closed => { - trace!("IP broadcast sink finished draining: channel closed"); - break; - } - RecvError::Lagged(_) => { - warn!("IP broadcast sink is falling behind"); - } - }, - } - } - - trace!("Stopped IP broadcast sink drain"); - } - fn create_tunnel(config: &Config) -> anyhow::Result> { Tunn::new( config.private_key.clone(), @@ -278,14 +258,9 @@ impl WireGuardTunnel { // Only care if the packet is destined for this tunnel .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip) .map(|packet| match packet.protocol() { - IpProtocol::Tcp => Some(self.route_tcp_segment( - IpVersion::Ipv4, - packet.src_addr().into(), - packet.dst_addr().into(), - packet.payload(), - )), - // Unrecognized protocol, so we'll allow it. - _ => Some(RouteResult::Broadcast), + IpProtocol::Tcp => Some(self.route_tcp_segment(packet.payload())), + // Unrecognized protocol, so we cannot determine where to route + _ => Some(RouteResult::Drop), }) .flatten() .unwrap_or(RouteResult::Drop), @@ -294,14 +269,9 @@ impl WireGuardTunnel { // Only care if the packet is destined for this tunnel .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip) .map(|packet| match packet.next_header() { - IpProtocol::Tcp => Some(self.route_tcp_segment( - IpVersion::Ipv6, - packet.src_addr().into(), - packet.dst_addr().into(), - packet.payload(), - )), - // Unrecognized protocol, so we'll allow it. - _ => Some(RouteResult::Broadcast), + IpProtocol::Tcp => Some(self.route_tcp_segment(packet.payload())), + // Unrecognized protocol, so we cannot determine where to route + _ => Some(RouteResult::Drop), }) .flatten() .unwrap_or(RouteResult::Drop), @@ -310,40 +280,32 @@ impl WireGuardTunnel { } /// Makes a decision on the handling of an incoming TCP segment. - fn route_tcp_segment( - &self, - ip_version: IpVersion, - src_addr: IpAddress, - dst_addr: IpAddress, - segment: &[u8], - ) -> RouteResult { + fn route_tcp_segment(&self, segment: &[u8]) -> RouteResult { TcpPacket::new_checked(segment) .ok() .map(|tcp| { - if self.port_pool.is_in_use(tcp.dst_port()) { - RouteResult::Broadcast + if self.virtual_port_ip_tx.get(&tcp.dst_port()).is_some() { + RouteResult::Dispatch(tcp.dst_port()) } else if tcp.rst() { RouteResult::Drop } else { - // Port is not in use, but it's a TCP packet so we'll craft a RST. - RouteResult::TcpReset(craft_tcp_rst_reply( - ip_version, - src_addr, - tcp.src_port(), - dst_addr, - tcp.dst_port(), - tcp.ack_number(), - )) + RouteResult::TcpReset } }) .unwrap_or(RouteResult::Drop) } + + /// Route a packet to the TCP sink interface. + async fn route_tcp_sink(&self, _packet: &[u8]) -> anyhow::Result<()> { + // TODO + Ok(()) + } } /// Craft an IP packet containing a TCP RST segment, given an IP version, /// source address (the one to reply to), destination address (the one the reply comes from), /// and the ACK number received in the initiating TCP segment. -fn craft_tcp_rst_reply( +fn _craft_tcp_rst_reply( ip_version: IpVersion, source_addr: IpAddress, source_port: u16, @@ -450,10 +412,10 @@ fn trace_ip_packet(message: &str, packet: &[u8]) { } enum RouteResult { - /// The packet can be broadcasted to the virtual interfaces - Broadcast, + /// Dispatch the packet to the virtual port. + Dispatch(u16), /// The packet is not routable so it may be reset. - TcpReset(Vec), + TcpReset, /// The packet can be safely ignored. Drop, } From 2bcb9e714b3b31178b0d3b68da1a23e1df05afd9 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 19:38:47 -0400 Subject: [PATCH 004/165] Implement IP sink interface --- src/ip_sink.rs | 35 +++++++++++++++++++++++++++++++++++ src/main.rs | 8 +++++++- src/virtual_device.rs | 8 ++++++++ src/wg.rs | 37 ++++++++++++++++++++++++++++++++----- 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 src/ip_sink.rs diff --git a/src/ip_sink.rs b/src/ip_sink.rs new file mode 100644 index 0000000..f17eb21 --- /dev/null +++ b/src/ip_sink.rs @@ -0,0 +1,35 @@ +use crate::virtual_device::VirtualIpDevice; +use crate::wg::WireGuardTunnel; +use smoltcp::iface::InterfaceBuilder; +use smoltcp::socket::SocketSet; +use std::sync::Arc; +use tokio::time::Duration; + +/// A repeating task that processes unroutable IP packets. +pub async fn run_ip_sink_interface(wg: Arc) -> ! { + // Initialize interface + let device = VirtualIpDevice::new_sink(wg) + .await + .expect("Failed to initialize VirtualIpDevice for sink interface"); + + // No sockets on sink interface + let mut socket_set_entries: [_; 0] = Default::default(); + let mut socket_set = SocketSet::new(&mut socket_set_entries[..]); + let mut virtual_interface = InterfaceBuilder::new(device).ip_addrs([]).finalize(); + + loop { + let loop_start = smoltcp::time::Instant::now(); + match virtual_interface.poll(&mut socket_set, loop_start) { + Ok(processed) if processed => { + trace!("[SINK] Virtual interface polled some packets to be processed",); + tokio::time::sleep(Duration::from_millis(1)).await; + } + Err(e) => { + error!("[SINK] Virtual interface poll error: {:?}", e); + } + _ => { + tokio::time::sleep(Duration::from_millis(5)).await; + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 7ae3e0f..afacddd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ use crate::virtual_device::VirtualIpDevice; use crate::wg::WireGuardTunnel; pub mod config; +pub mod ip_sink; pub mod port_pool; pub mod virtual_device; pub mod wg; @@ -47,6 +48,12 @@ async fn main() -> anyhow::Result<()> { tokio::spawn(async move { wg.consume_task().await }); } + { + // Start IP sink task for incoming IP packets + let wg = wg.clone(); + tokio::spawn(async move { ip_sink::run_ip_sink_interface(wg).await }); + } + info!( "Tunnelling [{}]->[{}] (via [{}] as peer {})", &config.source_addr, &config.dest_addr, &config.endpoint_addr, &config.source_peer_ip @@ -278,7 +285,6 @@ async fn virtual_tcp_interface( IpCidr::new(IpAddress::from(source_peer_ip), 32), IpCidr::new(IpAddress::from(dest_addr.ip()), 32), ]) - .any_ip(true) .finalize(); // Server socket: this is a placeholder for the interface to route new connections to. diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 3ca2416..b8a61ce 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -22,6 +22,14 @@ impl VirtualIpDevice { Ok(Self { wg, ip_dispatch_rx }) } + + pub async fn new_sink(wg: Arc) -> anyhow::Result { + let ip_dispatch_rx = wg + .register_sink_interface() + .await + .with_context(|| "Failed to register IP dispatch for sink virtual interface")?; + Ok(Self { wg, ip_dispatch_rx }) + } } impl<'a> Device<'a> for VirtualIpDevice { diff --git a/src/wg.rs b/src/wg.rs index f748794..f91f6d1 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -10,6 +10,7 @@ use smoltcp::wire::{ TcpPacket, TcpRepr, TcpSeqNumber, }; use tokio::net::UdpSocket; +use tokio::sync::RwLock; use crate::config::Config; use crate::MAX_PACKET; @@ -30,6 +31,8 @@ pub struct WireGuardTunnel { endpoint: SocketAddr, /// Maps virtual ports to the corresponding IP packet dispatcher. virtual_port_ip_tx: lockfree::map::Map>>, + /// IP packet dispatcher for unroutable packets. `None` if not initialized. + sink_ip_tx: RwLock>>>, } impl WireGuardTunnel { @@ -49,6 +52,7 @@ impl WireGuardTunnel { udp, endpoint, virtual_port_ip_tx, + sink_ip_tx: RwLock::new(None), }) } @@ -98,6 +102,18 @@ impl WireGuardTunnel { } } + /// Register a virtual interface (using its assigned virtual port) with the given IP packet `Sender`. + pub async fn register_sink_interface( + &self, + ) -> anyhow::Result>> { + let (sender, receiver) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); + + let mut sink_ip_tx = self.sink_ip_tx.write().await; + *sink_ip_tx = Some(sender); + + Ok(receiver) + } + /// Releases the virtual interface from IP dispatch. pub fn release_virtual_interface(&self, virtual_port: u16) { self.virtual_port_ip_tx.remove(&virtual_port); @@ -223,7 +239,7 @@ impl WireGuardTunnel { } RouteResult::TcpReset => { trace!("Resetting dead TCP connection after packet from WireGuard endpoint"); - self.route_tcp_sink(packet).await.unwrap_or_else(|e| { + self.route_ip_sink(packet).await.unwrap_or_else(|e| { error!("Failed to send TCP reset to sink: {:?}", e) }); } @@ -295,10 +311,21 @@ impl WireGuardTunnel { .unwrap_or(RouteResult::Drop) } - /// Route a packet to the TCP sink interface. - async fn route_tcp_sink(&self, _packet: &[u8]) -> anyhow::Result<()> { - // TODO - Ok(()) + /// Route a packet to the IP sink interface. + async fn route_ip_sink(&self, packet: &[u8]) -> anyhow::Result<()> { + let ip_sink_tx = self.sink_ip_tx.read().await; + + if let Some(ip_sink_tx) = &*ip_sink_tx { + ip_sink_tx + .send(packet.to_vec()) + .await + .with_context(|| "Failed to dispatch IP packet to sink interface") + } else { + warn!( + "Could not dispatch unroutable IP packet to sink because interface is not active." + ); + Ok(()) + } } } From 84fbcd4fad85e7e07c715c1988ae037bab78e79a Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 19:43:27 -0400 Subject: [PATCH 005/165] Clean-up unused code for TCP RST --- src/wg.rs | 111 +++++------------------------------------------------- 1 file changed, 9 insertions(+), 102 deletions(-) diff --git a/src/wg.rs b/src/wg.rs index f91f6d1..7fc5559 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -4,11 +4,7 @@ use std::time::Duration; use anyhow::Context; use boringtun::noise::{Tunn, TunnResult}; use log::Level; -use smoltcp::phy::ChecksumCapabilities; -use smoltcp::wire::{ - IpAddress, IpProtocol, IpVersion, Ipv4Packet, Ipv4Repr, Ipv6Packet, Ipv6Repr, TcpControl, - TcpPacket, TcpRepr, TcpSeqNumber, -}; +use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet, TcpPacket}; use tokio::net::UdpSocket; use tokio::sync::RwLock; @@ -237,14 +233,14 @@ impl WireGuardTunnel { warn!("[{}] Race condition: failed to get virtual port sender after it was dispatched", port); } } - RouteResult::TcpReset => { - trace!("Resetting dead TCP connection after packet from WireGuard endpoint"); + RouteResult::Sink => { + trace!("Sending unroutable IP packet received from WireGuard endpoint to sink interface"); self.route_ip_sink(packet).await.unwrap_or_else(|e| { - error!("Failed to send TCP reset to sink: {:?}", e) + error!("Failed to send unroutable IP packet to sink: {:?}", e) }); } RouteResult::Drop => { - trace!("Dropped incoming IP packet from WireGuard endpoint"); + trace!("Dropped unroutable IP packet received from WireGuard endpoint"); } } } @@ -305,7 +301,7 @@ impl WireGuardTunnel { } else if tcp.rst() { RouteResult::Drop } else { - RouteResult::TcpReset + RouteResult::Sink } }) .unwrap_or(RouteResult::Drop) @@ -329,95 +325,6 @@ impl WireGuardTunnel { } } -/// Craft an IP packet containing a TCP RST segment, given an IP version, -/// source address (the one to reply to), destination address (the one the reply comes from), -/// and the ACK number received in the initiating TCP segment. -fn _craft_tcp_rst_reply( - ip_version: IpVersion, - source_addr: IpAddress, - source_port: u16, - dest_addr: IpAddress, - dest_port: u16, - ack_number: TcpSeqNumber, -) -> Vec { - let tcp_repr = TcpRepr { - src_port: dest_port, - dst_port: source_port, - control: TcpControl::Rst, - seq_number: ack_number, - ack_number: None, - window_len: 0, - window_scale: None, - max_seg_size: None, - sack_permitted: false, - sack_ranges: [None, None, None], - payload: &[], - }; - - let mut tcp_buffer = vec![0u8; 20]; - let mut tcp_packet = &mut TcpPacket::new_unchecked(&mut tcp_buffer); - tcp_repr.emit( - &mut tcp_packet, - &dest_addr, - &source_addr, - &ChecksumCapabilities::default(), - ); - - let mut ip_buffer = vec![0u8; MAX_PACKET]; - - let (header_len, total_len) = match ip_version { - IpVersion::Ipv4 => { - let dest_addr = match dest_addr { - IpAddress::Ipv4(dest_addr) => dest_addr, - _ => panic!(), - }; - let source_addr = match source_addr { - IpAddress::Ipv4(source_addr) => source_addr, - _ => panic!(), - }; - - let mut ip_packet = &mut Ipv4Packet::new_unchecked(&mut ip_buffer); - let ip_repr = Ipv4Repr { - src_addr: dest_addr, - dst_addr: source_addr, - protocol: IpProtocol::Tcp, - payload_len: tcp_buffer.len(), - hop_limit: 64, - }; - ip_repr.emit(&mut ip_packet, &ChecksumCapabilities::default()); - ( - ip_packet.header_len() as usize, - ip_packet.total_len() as usize, - ) - } - IpVersion::Ipv6 => { - let dest_addr = match dest_addr { - IpAddress::Ipv6(dest_addr) => dest_addr, - _ => panic!(), - }; - let source_addr = match source_addr { - IpAddress::Ipv6(source_addr) => source_addr, - _ => panic!(), - }; - let mut ip_packet = &mut Ipv6Packet::new_unchecked(&mut ip_buffer); - let ip_repr = Ipv6Repr { - src_addr: dest_addr, - dst_addr: source_addr, - next_header: IpProtocol::Tcp, - payload_len: tcp_buffer.len(), - hop_limit: 64, - }; - ip_repr.emit(&mut ip_packet); - (ip_packet.header_len(), ip_packet.total_len()) - } - _ => panic!(), - }; - - ip_buffer[header_len..total_len].copy_from_slice(&tcp_buffer); - let packet: &[u8] = &ip_buffer[..total_len]; - packet.to_vec() -} - fn trace_ip_packet(message: &str, packet: &[u8]) { if log_enabled!(Level::Trace) { use smoltcp::wire::*; @@ -441,8 +348,8 @@ fn trace_ip_packet(message: &str, packet: &[u8]) { enum RouteResult { /// Dispatch the packet to the virtual port. Dispatch(u16), - /// The packet is not routable so it may be reset. - TcpReset, - /// The packet can be safely ignored. + /// The packet is not routable, and should be sent to the sink interface. + Sink, + /// The packet is not routable, and can be safely ignored. Drop, } From 4b370b163c3fdf3b1e5c66f6afe4e11c0a31fbca Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 19:46:53 -0400 Subject: [PATCH 006/165] Reword readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b7681e..c86dc63 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,8 @@ forward it to the server's port, which handles the TCP segment. The server respo the peer's local WireGuard interface, gets encrypted, forwarded to the WireGuard endpoint, and then finally back to onetun's UDP port. When onetun receives an encrypted packet from the WireGuard endpoint, it decrypts it using boringtun. -The resulting IP packet is dispatched to the corresponding virtual interface running inside onetun; once the corresponding -interface is matched, the IP packet is read and unpacked, and the virtual client's TCP state is updated. +The resulting IP packet is dispatched to the corresponding virtual interface running inside onetun; +the IP packet is then read and processed by the virtual interface, and the virtual client's TCP state is updated. Whenever data is sent by the real client, it is simply "sent" by the virtual client, which kicks off the whole IP encapsulation and WireGuard encryption again. When data is sent by the real server, it ends up routed in the virtual interface, which allows From a70e57ff1dbe1b9c1c3a8635171b57a6ecf77266 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 19:47:42 -0400 Subject: [PATCH 007/165] release: v0.1.8 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bd7c43..3611615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.1.7" +version = "0.1.8" dependencies = [ "anyhow", "boringtun", diff --git a/Cargo.toml b/Cargo.toml index bafce0b..ae6d317 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.1.7" +version = "0.1.8" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From fa92c7549797ca3ce2ab0faa62d136c3399d984c Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 20:45:17 -0400 Subject: [PATCH 008/165] Attempt to not cause graceful shutdown too quickly --- src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index afacddd..4337461 100644 --- a/src/main.rs +++ b/src/main.rs @@ -420,7 +420,11 @@ async fn virtual_tcp_interface( } } } - if !graceful_shutdown && !forceful_shutdown && !client_socket.is_active() { + if !graceful_shutdown + && !forceful_shutdown + && !client_socket.is_active() + && !client_socket.can_recv() + { // Graceful shutdown client_socket.close(); trace!( From 43c07c4691c4c92818c29412194e64b930ec9ee3 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 20:53:05 -0400 Subject: [PATCH 009/165] Add simpler use-cast to README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index c86dc63..f652574 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,16 @@ A cross-platform, user-space WireGuard port-forwarder that requires no system ne [![Build status](https://github.com/aramperes/onetun/actions/workflows/build.yml/badge.svg)](https://github.com/aramperes/onetun/actions) [![Latest Release](https://img.shields.io/github/v/tag/aramperes/onetun?label=release)](https://github.com/aramperes/onetun/releases/latest) +## Use-case + +- You have an existing WireGuard endpoint (router), accessible using its UDP endpoint (typically port 51820); and +- You have a peer on the WireGuard network, running a TCP server on a port accessible to the WireGuard network; and +- You want to access this TCP service from a second computer, on which you can't install WireGuard because you + can't (no root access) or don't want to (polluting OS configs). + +For example, this can be useful to forward a port from a Kubernetes cluster to a server behind WireGuard, +without needing to install WireGuard in a Pod. + ## Usage **onetun** opens a TCP port on your local system, from which traffic is forwarded to a TCP port on a peer in your From 479297f64d4ea808c852d3d8fa54baaa1885ab73 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 20:53:43 -0400 Subject: [PATCH 010/165] release: v0.1.9 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3611615..7656513 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.1.8" +version = "0.1.9" dependencies = [ "anyhow", "boringtun", diff --git a/Cargo.toml b/Cargo.toml index ae6d317..284c7f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.1.8" +version = "0.1.9" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 5e6cfe8955ba2e5949e9e5e1784608d323cc216c Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 21:12:08 -0400 Subject: [PATCH 011/165] Don't break TCP connection until all data is flushed Potential fix for #14 --- src/main.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4337461..8384d3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -179,10 +179,6 @@ async fn handle_tcp_proxy_connection( trace!("[{}] Virtual client is ready to send data", virtual_port); loop { - if abort.load(Ordering::Relaxed) { - break; - } - tokio::select! { readable_result = socket.readable() => { match readable_result { @@ -234,7 +230,11 @@ async fn handle_tcp_proxy_connection( ); } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - continue; + if abort.load(Ordering::Relaxed) { + break; + } else { + continue; + } } Err(e) => { error!( @@ -243,7 +243,13 @@ async fn handle_tcp_proxy_connection( ); } }, - None => continue, + None => { + if abort.load(Ordering::Relaxed) { + break; + } else { + continue; + } + }, } } } From 87f3023f8ba9b9fea27f355d2f9bb20c6850c26c Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 21:12:51 -0400 Subject: [PATCH 012/165] release: v0.1.10 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7656513..0d286ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.1.9" +version = "0.1.10" dependencies = [ "anyhow", "boringtun", diff --git a/Cargo.toml b/Cargo.toml index 284c7f5..15d5047 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.1.9" +version = "0.1.10" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 9da88a0d469fafbd6d2a88f608b20e9d9cc6cc5a Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 22:53:42 -0400 Subject: [PATCH 013/165] Close interface after server FIN; no more data to send. Potential fix for #14 --- src/main.rs | 47 +++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8384d3e..097ea9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::time::Duration; use anyhow::Context; use smoltcp::iface::InterfaceBuilder; -use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; +use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer, TcpState}; use smoltcp::wire::{IpAddress, IpCidr}; use tokio::net::{TcpListener, TcpStream}; @@ -332,22 +332,20 @@ async fn virtual_tcp_interface( let _server_handle = socket_set.add(server_socket?); let client_handle = socket_set.add(client_socket?); - // Instructs that this is the last poll, after which the connection is closed. - let mut graceful_shutdown = false; - // Any data that wasn't sent because it was over the sending buffer limit let mut tx_extra = Vec::new(); loop { let loop_start = smoltcp::time::Instant::now(); - let forceful_shutdown = abort.load(Ordering::Relaxed); - if forceful_shutdown { - // Un-graceful shutdown: sends a RST packet. - trace!( - "[{}] Forcefully shutting down virtual interface", - virtual_port - ); + // Shutdown occurs when the real client closes the connection, + // or if the client was in a CLOSE-WAIT state (after a server FIN) and had no data to send anmore. + // One last poll-loop iteration is executed so that the RST segment can be dispatched. + let shutdown = abort.load(Ordering::Relaxed); + + if shutdown { + // Shutdown: sends a RST packet. + trace!("[{}] Shutting down virtual interface", virtual_port); let mut client_socket = socket_set.get::(client_handle); client_socket.abort(); } @@ -367,6 +365,7 @@ async fn virtual_tcp_interface( { let mut client_socket = socket_set.get::(client_handle); + if client_socket.can_recv() { match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { Ok(data) => { @@ -398,9 +397,16 @@ async fn virtual_tcp_interface( let mut to_transfer = None; if tx_extra.is_empty() { - // We can read the next data in the queue + // The payload segment from the previous loop is complete, + // we can now read the next payload in the queue. if let Ok(data) = data_to_virtual_server_rx.try_recv() { to_transfer = Some(data); + } else if client_socket.state() == TcpState::CloseWait { + // No data to be sent in this loop. If the client state is CLOSE-WAIT (because of a server FIN), + // the interface is shutdown. + trace!("[{}] Shutting down virtual interface because client sent no more data, and server sent FIN (CLOSE-WAIT)", virtual_port); + abort.store(true, Ordering::Relaxed); + continue; } } @@ -426,24 +432,9 @@ async fn virtual_tcp_interface( } } } - if !graceful_shutdown - && !forceful_shutdown - && !client_socket.is_active() - && !client_socket.can_recv() - { - // Graceful shutdown - client_socket.close(); - trace!( - "[{}] Gracefully shutting down virtual interface", - virtual_port - ); - // We don't break the loop right away so that the FIN segment can be sent in the next poll. - graceful_shutdown = true; - continue; - } } - if graceful_shutdown || forceful_shutdown { + if shutdown { break; } From c4a5e634abcd84103d9ecdc2489846b63c9c78f3 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 16 Oct 2021 23:36:46 -0400 Subject: [PATCH 014/165] release: v0.1.11 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d286ad..27fb027 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.1.10" +version = "0.1.11" dependencies = [ "anyhow", "boringtun", diff --git a/Cargo.toml b/Cargo.toml index 15d5047..2abd992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.1.10" +version = "0.1.11" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From cb09bb885743603e8a582eed0aabd41fec1fb09a Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 17 Oct 2021 21:57:41 -0400 Subject: [PATCH 015/165] WIP on UDP and multi-port-forward support --- Cargo.lock | 18 +++++++ Cargo.toml | 1 + src/config.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 61 ++++++++++++++++----- src/wg.rs | 2 +- 5 files changed, 197 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27fb027..edcdee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -488,6 +488,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "minimal-lexical" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677" + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -520,6 +526,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "nom" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -583,6 +600,7 @@ dependencies = [ "futures", "lockfree", "log", + "nom", "pretty_env_logger", "rand", "smoltcp", diff --git a/Cargo.toml b/Cargo.toml index 2abd992..a6f927f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ tokio = { version = "1", features = ["full"] } lockfree = "0.5.1" futures = "0.3.17" rand = "0.8.4" +nom = "7" diff --git a/src/config.rs b/src/config.rs index 2c1a993..080232d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,6 @@ +use std::collections::HashSet; +use std::convert::TryFrom; +use std::fmt::{Display, Formatter}; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::sync::Arc; @@ -7,8 +10,7 @@ use clap::{App, Arg}; #[derive(Clone, Debug)] pub struct Config { - pub(crate) source_addr: SocketAddr, - pub(crate) dest_addr: SocketAddr, + pub(crate) port_forwards: Vec, pub(crate) private_key: Arc, pub(crate) endpoint_public_key: Arc, pub(crate) endpoint_addr: SocketAddr, @@ -23,16 +25,12 @@ impl Config { .author("Aram Peres ") .version(env!("CARGO_PKG_VERSION")) .args(&[ - Arg::with_name("SOURCE_ADDR") - .required(true) + Arg::with_name("PORT_FORWARD") + .required(false) + .multiple(true) .takes_value(true) - .env("ONETUN_SOURCE_ADDR") - .help("The source address (IP + port) to forward from. Example: 127.0.0.1:2115"), - Arg::with_name("DESTINATION_ADDR") - .required(true) - .takes_value(true) - .env("ONETUN_DESTINATION_ADDR") - .help("The destination address (IP + port) to forward to. The IP should be a peer registered in the Wireguard endpoint. Example: 192.168.4.2:2116"), + .help("Port forward configurations. The format of each argument is [src_host:]::[:TCP,UDP,...]. \ + Environment variables of the form 'ONETUN_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1."), Arg::with_name("private-key") .required(true) .takes_value(true) @@ -72,11 +70,40 @@ impl Config { .help("Configures the log level and format.") ]).get_matches(); + // Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` strings + let mut port_forward_strings = HashSet::new(); + matches.values_of("PORT_FORWARD").map(|values| { + values + .into_iter() + .map(|v| port_forward_strings.insert(v.to_string())) + .map(|_| ()) + }); + for n in 1.. { + if let Ok(env) = std::env::var(format!("ONETUN_PORT_FORWARD_{}", n)) { + port_forward_strings.insert(env); + } else { + break; + } + } + if port_forward_strings.is_empty() { + return Err(anyhow::anyhow!("No port forward configurations given.")); + } + + // Parse `PORT_FORWARD` strings into `PortForwardConfig` + let port_forwards: Vec>> = port_forward_strings + .into_iter() + .map(|s| PortForwardConfig::from_str(&s)) + .collect(); + let port_forwards: anyhow::Result>> = + port_forwards.into_iter().collect(); + let port_forwards: Vec = port_forwards + .with_context(|| "Failed to parse port forward config")? + .into_iter() + .flatten() + .collect(); + Ok(Self { - source_addr: parse_addr(matches.value_of("SOURCE_ADDR")) - .with_context(|| "Invalid source address")?, - dest_addr: parse_addr(matches.value_of("DESTINATION_ADDR")) - .with_context(|| "Invalid destination address")?, + port_forwards, private_key: Arc::new( parse_private_key(matches.value_of("private-key")) .with_context(|| "Invalid private key")?, @@ -137,3 +164,89 @@ fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { Ok(None) } } + +#[derive(Debug, Clone, Copy)] +pub struct PortForwardConfig { + /// The source IP and port where the local server will run. + pub source: SocketAddr, + /// The destination IP and port to which traffic will be forwarded. + pub destination: SocketAddr, + /// The transport protocol to use for the port (Layer 4). + pub protocol: PortProtocol, +} + +impl PortForwardConfig { + /// Converts a string representation into `PortForwardConfig`. + /// + /// Sample formats: + /// - `127.0.0.1:8080:192.168.4.1:8081:TCP,UDP` + /// - `127.0.0.1:8080:192.168.4.1:8081:TCP` + /// - `0.0.0.0:8080:192.168.4.1:8081` + /// - `[::1]:8080:192.168.4.1:8081` + /// - `8080:192.168.4.1:8081` + /// - `8080:192.168.4.1:8081:TCP` + /// + /// Implementation Notes: + /// - The format is formalized as `[src_host:]::[:PROTO1,PROTO2,...]` + /// - `src_host` is optional and defaults to `127.0.0.1`. + /// - `src_host` and `dst_host` may be specified as IPv4, IPv6, or a FQDN to be resolved by DNS. + /// - IPv6 addresses must be prefixed with `[` and suffixed with `]`. Example: `[::1]`. + /// - Any `u16` is accepted as `src_port` and `dst_port` + /// - Specifying protocols (`PROTO1,PROTO2,...`) is optional and defaults to `TCP`. Values must be separated by commas. + pub fn from_str<'a>(s: &'a str) -> anyhow::Result> { + use nom::branch::alt; + use nom::bytes::complete::{is_not, take_until, take_while}; + use nom::character::complete::char; + use nom::combinator::opt; + use nom::multi::separated_list0; + use nom::sequence::{delimited, terminated}; + use nom::IResult; + + Err(anyhow::anyhow!("TODO")) + } +} + +impl Display for PortForwardConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}:{}", self.source, self.destination, self.protocol) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum PortProtocol { + Tcp, + Udp, +} + +impl TryFrom<&str> for PortProtocol { + type Error = anyhow::Error; + + fn try_from(value: &str) -> anyhow::Result { + match value.to_uppercase().as_str() { + "TCP" => Ok(Self::Tcp), + "UDP" => Ok(Self::Udp), + _ => Err(anyhow::anyhow!("Invalid protocol specifier: {}", value)), + } + } +} + +impl Display for PortProtocol { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Tcp => "TCP", + Self::Udp => "UDP", + } + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the parsing of `PortForwardConfig`. + fn test_parse_port_forward_config() {} +} diff --git a/src/main.rs b/src/main.rs index 097ea9c..bb3dc34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer, TcpState}; use smoltcp::wire::{IpAddress, IpCidr}; use tokio::net::{TcpListener, TcpStream}; -use crate::config::Config; +use crate::config::{Config, PortForwardConfig, PortProtocol}; use crate::port_pool::PortPool; use crate::virtual_device::VirtualIpDevice; use crate::wg::WireGuardTunnel; @@ -54,26 +54,63 @@ async fn main() -> anyhow::Result<()> { tokio::spawn(async move { ip_sink::run_ip_sink_interface(wg).await }); } + { + let port_forwards = config.port_forwards; + let source_peer_ip = config.source_peer_ip; + + futures::future::try_join_all( + port_forwards + .into_iter() + .map(|pf| (pf, wg.clone(), port_pool.clone())) + .map(|(pf, wg, port_pool)| { + tokio::spawn(async move { + port_forward(pf, source_peer_ip, port_pool, wg) + .await + .with_context(|| format!("Port-forward failed: {})", pf)) + }) + }), + ) + .await + .with_context(|| "A port-forward instance failed.") + .map(|_| ()) + } +} + +async fn port_forward( + port_forward: PortForwardConfig, + source_peer_ip: IpAddr, + port_pool: Arc, + wg: Arc, +) -> anyhow::Result<()> { info!( - "Tunnelling [{}]->[{}] (via [{}] as peer {})", - &config.source_addr, &config.dest_addr, &config.endpoint_addr, &config.source_peer_ip + "Tunnelling {} [{}]->[{}] (via [{}] as peer {})", + port_forward.protocol, + port_forward.source, + port_forward.destination, + &wg.endpoint, + source_peer_ip ); - tcp_proxy_server( - config.source_addr, - config.source_peer_ip, - config.dest_addr, - port_pool.clone(), - wg, - ) - .await + match port_forward.protocol { + PortProtocol::Tcp => { + tcp_proxy_server( + port_forward.source, + port_forward.destination, + source_peer_ip, + port_pool, + wg, + ) + .await + } + PortProtocol::Udp => Err(anyhow::anyhow!("UDP isn't supported just yet.")), + } } /// Starts the server that listens on TCP connections. async fn tcp_proxy_server( listen_addr: SocketAddr, - source_peer_ip: IpAddr, dest_addr: SocketAddr, + source_peer_ip: IpAddr, port_pool: Arc, wg: Arc, ) -> anyhow::Result<()> { diff --git a/src/wg.rs b/src/wg.rs index 7fc5559..e2740e8 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -24,7 +24,7 @@ pub struct WireGuardTunnel { /// The UDP socket for the public WireGuard endpoint to connect to. udp: UdpSocket, /// The address of the public WireGuard endpoint (UDP). - endpoint: SocketAddr, + pub(crate) endpoint: SocketAddr, /// Maps virtual ports to the corresponding IP packet dispatcher. virtual_port_ip_tx: lockfree::map::Map>>, /// IP packet dispatcher for unroutable packets. `None` if not initialized. From 651ddaec494084129bb63f0a033f0f29e8ec5cea Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 18 Oct 2021 01:36:40 -0400 Subject: [PATCH 016/165] Implement port-forwarder configuration parsing --- src/config.rs | 279 +++++++++++++++++++++++++++++++++++++++++++++----- src/main.rs | 2 +- 2 files changed, 255 insertions(+), 26 deletions(-) diff --git a/src/config.rs b/src/config.rs index 080232d..3a46881 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,8 +29,19 @@ impl Config { .required(false) .multiple(true) .takes_value(true) - .help("Port forward configurations. The format of each argument is [src_host:]::[:TCP,UDP,...]. \ - Environment variables of the form 'ONETUN_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1."), + .help("Port forward configurations. The format of each argument is [src_host:]::[:TCP,UDP,...], \ + where [src_host] is the local IP to listen on, is the local port to listen on, is the remote peer IP to forward to, and is the remote port to forward to. \ + Environment variables of the form 'ONETUN_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1.\n\ + Examples:\n\ + \t127.0.0.1:8080:192.168.4.1:8081:TCP,UDP\n\ + \t127.0.0.1:8080:192.168.4.1:8081:TCP\n\ + \t0.0.0.0:8080:192.168.4.1:8081\n\ + \t[::1]:8080:192.168.4.1:8081\n\ + \t8080:192.168.4.1:8081\n\ + \t8080:192.168.4.1:8081:TCP\n\ + \tlocalhost:8080:192.168.4.1:8081:TCP\n\ + \tlocalhost:8080:peer.intranet:8081:TCP\ + "), Arg::with_name("private-key") .required(true) .takes_value(true) @@ -70,14 +81,13 @@ impl Config { .help("Configures the log level and format.") ]).get_matches(); - // Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` strings + // Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs let mut port_forward_strings = HashSet::new(); - matches.values_of("PORT_FORWARD").map(|values| { - values - .into_iter() - .map(|v| port_forward_strings.insert(v.to_string())) - .map(|_| ()) - }); + if let Some(values) = matches.values_of("PORT_FORWARD") { + for value in values { + port_forward_strings.insert(value.to_owned()); + } + } for n in 1.. { if let Ok(env) = std::env::var(format!("ONETUN_PORT_FORWARD_{}", n)) { port_forward_strings.insert(env); @@ -90,12 +100,10 @@ impl Config { } // Parse `PORT_FORWARD` strings into `PortForwardConfig` - let port_forwards: Vec>> = port_forward_strings + let port_forwards: anyhow::Result>> = port_forward_strings .into_iter() - .map(|s| PortForwardConfig::from_str(&s)) + .map(|s| PortForwardConfig::from_notation(&s)) .collect(); - let port_forwards: anyhow::Result>> = - port_forwards.into_iter().collect(); let port_forwards: Vec = port_forwards .with_context(|| "Failed to parse port forward config")? .into_iter() @@ -165,7 +173,7 @@ fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct PortForwardConfig { /// The source IP and port where the local server will run. pub source: SocketAddr, @@ -185,6 +193,8 @@ impl PortForwardConfig { /// - `[::1]:8080:192.168.4.1:8081` /// - `8080:192.168.4.1:8081` /// - `8080:192.168.4.1:8081:TCP` + /// - `localhost:8080:192.168.4.1:8081:TCP` + /// - `localhost:8080:peer.intranet:8081:TCP` /// /// Implementation Notes: /// - The format is formalized as `[src_host:]::[:PROTO1,PROTO2,...]` @@ -193,16 +203,126 @@ impl PortForwardConfig { /// - IPv6 addresses must be prefixed with `[` and suffixed with `]`. Example: `[::1]`. /// - Any `u16` is accepted as `src_port` and `dst_port` /// - Specifying protocols (`PROTO1,PROTO2,...`) is optional and defaults to `TCP`. Values must be separated by commas. - pub fn from_str<'a>(s: &'a str) -> anyhow::Result> { - use nom::branch::alt; - use nom::bytes::complete::{is_not, take_until, take_while}; - use nom::character::complete::char; - use nom::combinator::opt; - use nom::multi::separated_list0; - use nom::sequence::{delimited, terminated}; - use nom::IResult; + pub fn from_notation(s: &str) -> anyhow::Result> { + mod parsers { + use nom::branch::alt; + use nom::bytes::complete::is_not; + use nom::character::complete::{alpha1, char, digit1}; + use nom::combinator::{complete, map, opt, success}; + use nom::error::ErrorKind; + use nom::multi::separated_list1; + use nom::sequence::{delimited, preceded, separated_pair, tuple}; + use nom::IResult; - Err(anyhow::anyhow!("TODO")) + fn ipv6(s: &str) -> IResult<&str, &str> { + delimited(char('['), is_not("]"), char(']'))(s) + } + + fn ipv4_or_fqdn(s: &str) -> IResult<&str, &str> { + let s = is_not(":")(s)?; + if s.1.chars().all(|c| c.is_ascii_digit()) { + // If ipv4 or fqdn is all digits, it's not valid. + Err(nom::Err::Error(nom::error::ParseError::from_error_kind( + s.1, + ErrorKind::Fail, + ))) + } else { + Ok(s) + } + } + + fn port(s: &str) -> IResult<&str, &str> { + digit1(s) + } + + fn ip_or_fqdn(s: &str) -> IResult<&str, &str> { + alt((ipv6, ipv4_or_fqdn))(s) + } + + fn no_ip(s: &str) -> IResult<&str, Option<&str>> { + success(None)(s) + } + + fn src_addr(s: &str) -> IResult<&str, (Option<&str>, &str)> { + let with_ip = separated_pair(map(ip_or_fqdn, Some), char(':'), port); + let without_ip = tuple((no_ip, port)); + alt((with_ip, without_ip))(s) + } + + fn dst_addr(s: &str) -> IResult<&str, (&str, &str)> { + separated_pair(ip_or_fqdn, char(':'), port)(s) + } + + fn protocol(s: &str) -> IResult<&str, &str> { + alpha1(s) + } + + fn protocols(s: &str) -> IResult<&str, Option>> { + opt(preceded(char(':'), separated_list1(char(','), protocol)))(s) + } + + #[allow(clippy::type_complexity)] + pub fn port_forward( + s: &str, + ) -> IResult<&str, ((Option<&str>, &str), (), (&str, &str), Option>)> + { + complete(tuple(( + src_addr, + map(char(':'), |_| ()), + dst_addr, + protocols, + )))(s) + } + } + + // TODO: Could improve error management with custom errors, so that the messages are more helpful. + let (src_addr, _, dst_addr, protocols) = parsers::port_forward(s) + .map_err(|e| anyhow::anyhow!("Invalid port-forward definition: {}", e))? + .1; + + let source = ( + src_addr.0.unwrap_or("127.0.0.1"), + src_addr + .1 + .parse::() + .with_context(|| "Invalid source port")?, + ) + .to_socket_addrs() + .with_context(|| "Invalid source address")? + .next() + .with_context(|| "Could not resolve source address")?; + + let destination = ( + dst_addr.0, + dst_addr + .1 + .parse::() + .with_context(|| "Invalid source port")?, + ) + .to_socket_addrs() // TODO: Pass this as given and use DNS config instead (issue #15) + .with_context(|| "Invalid destination address")? + .next() + .with_context(|| "Could not resolve destination address")?; + + // Parse protocols + let protocols = if let Some(protocols) = protocols { + let protocols: anyhow::Result> = + protocols.into_iter().map(PortProtocol::try_from).collect(); + protocols + } else { + Ok(vec![PortProtocol::Tcp]) + } + .with_context(|| "Failed to parse protocols")?; + + // Returns an config for each protocol + Ok(protocols + .into_iter() + .map(|protocol| Self { + source, + destination, + protocol, + }) + .collect()) } } @@ -212,7 +332,7 @@ impl Display for PortForwardConfig { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum PortProtocol { Tcp, Udp, @@ -245,8 +365,117 @@ impl Display for PortProtocol { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; /// Tests the parsing of `PortForwardConfig`. - fn test_parse_port_forward_config() {} + #[test] + fn test_parse_port_forward_config_1() { + assert_eq!( + PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP,UDP") + .expect("Failed to parse"), + vec![ + PortForwardConfig { + source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Tcp + }, + PortForwardConfig { + source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Udp + } + ] + ); + } + /// Tests the parsing of `PortForwardConfig`. + #[test] + fn test_parse_port_forward_config_2() { + assert_eq!( + PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP") + .expect("Failed to parse"), + vec![PortForwardConfig { + source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Tcp + }] + ); + } + /// Tests the parsing of `PortForwardConfig`. + #[test] + fn test_parse_port_forward_config_3() { + assert_eq!( + PortForwardConfig::from_notation("0.0.0.0:8080:192.168.4.1:8081") + .expect("Failed to parse"), + vec![PortForwardConfig { + source: SocketAddr::from_str("0.0.0.0:8080").unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Tcp + }] + ); + } + /// Tests the parsing of `PortForwardConfig`. + #[test] + fn test_parse_port_forward_config_4() { + assert_eq!( + PortForwardConfig::from_notation("[::1]:8080:192.168.4.1:8081") + .expect("Failed to parse"), + vec![PortForwardConfig { + source: SocketAddr::from_str("[::1]:8080").unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Tcp + }] + ); + } + /// Tests the parsing of `PortForwardConfig`. + #[test] + fn test_parse_port_forward_config_5() { + assert_eq!( + PortForwardConfig::from_notation("8080:192.168.4.1:8081").expect("Failed to parse"), + vec![PortForwardConfig { + source: SocketAddr::from_str("127.0.0.1:8080").unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Tcp + }] + ); + } + /// Tests the parsing of `PortForwardConfig`. + #[test] + fn test_parse_port_forward_config_6() { + assert_eq!( + PortForwardConfig::from_notation("8080:192.168.4.1:8081:TCP").expect("Failed to parse"), + vec![PortForwardConfig { + source: SocketAddr::from_str("127.0.0.1:8080").unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Tcp + }] + ); + } + /// Tests the parsing of `PortForwardConfig`. + #[test] + fn test_parse_port_forward_config_7() { + assert_eq!( + PortForwardConfig::from_notation("localhost:8080:192.168.4.1:8081") + .expect("Failed to parse"), + vec![PortForwardConfig { + source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(), + destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), + protocol: PortProtocol::Tcp + }] + ); + } + /// Tests the parsing of `PortForwardConfig`. + #[test] + fn test_parse_port_forward_config_8() { + assert_eq!( + PortForwardConfig::from_notation("localhost:8080:localhost:8081:TCP") + .expect("Failed to parse"), + vec![PortForwardConfig { + source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(), + destination: "localhost:8081".to_socket_addrs().unwrap().next().unwrap(), + protocol: PortProtocol::Tcp + }] + ); + } } diff --git a/src/main.rs b/src/main.rs index bb3dc34..0a8cd1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,7 @@ async fn main() -> anyhow::Result<()> { tokio::spawn(async move { port_forward(pf, source_peer_ip, port_pool, wg) .await - .with_context(|| format!("Port-forward failed: {})", pf)) + .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) }) }), ) From ed835c47d303af68fd02765d867eec3b4965bc2d Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 18 Oct 2021 03:54:13 -0400 Subject: [PATCH 017/165] Spawn tunnels in entirely separate threads --- src/main.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0a8cd1d..95eb37a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,22 +58,22 @@ async fn main() -> anyhow::Result<()> { let port_forwards = config.port_forwards; let source_peer_ip = config.source_peer_ip; - futures::future::try_join_all( - port_forwards - .into_iter() - .map(|pf| (pf, wg.clone(), port_pool.clone())) - .map(|(pf, wg, port_pool)| { - tokio::spawn(async move { + port_forwards + .into_iter() + .map(|pf| (pf, wg.clone(), port_pool.clone())) + .for_each(move |(pf, wg, port_pool)| { + std::thread::spawn(move || { + let cpu_pool = tokio::runtime::Runtime::new().unwrap(); + cpu_pool.block_on(async move { port_forward(pf, source_peer_ip, port_pool, wg) .await .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) - }) - }), - ) - .await - .with_context(|| "A port-forward instance failed.") - .map(|_| ()) + }); + }); + }); } + + futures::future::pending().await } async fn port_forward( From dbced52070e35a48b0298a01ba41c6b4d0264acd Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 18 Oct 2021 06:03:54 -0400 Subject: [PATCH 018/165] Attempt reconnection in virtual client --- src/main.rs | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 95eb37a..bd28f96 100644 --- a/src/main.rs +++ b/src/main.rs @@ -212,7 +212,7 @@ async fn handle_tcp_proxy_connection( // Wait for virtual client to be ready. virtual_client_ready_rx .await - .expect("failed to wait for virtual client to be ready"); + .with_context(|| "Virtual client dropped before being ready.")?; trace!("[{}] Virtual client is ready to send data", virtual_port); loop { @@ -351,15 +351,7 @@ async fn virtual_tcp_interface( static mut TCP_SERVER_TX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); - let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - - socket - .connect( - (IpAddress::from(dest_addr.ip()), dest_addr.port()), - (IpAddress::from(source_peer_ip), virtual_port), - ) - .with_context(|| "Virtual server socket failed to listen")?; - + let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); Ok(socket) }; @@ -372,11 +364,16 @@ async fn virtual_tcp_interface( // Any data that wasn't sent because it was over the sending buffer limit let mut tx_extra = Vec::new(); + // Counts the connection attempts by the virtual client + let mut connection_attempts = 0; + // Whether the client has successfully connected before. Prevents the case of connecting again. + let mut has_connected = false; + loop { let loop_start = smoltcp::time::Instant::now(); // Shutdown occurs when the real client closes the connection, - // or if the client was in a CLOSE-WAIT state (after a server FIN) and had no data to send anmore. + // or if the client was in a CLOSE-WAIT state (after a server FIN) and had no data to send anymore. // One last poll-loop iteration is executed so that the RST segment can be dispatched. let shutdown = abort.load(Ordering::Relaxed); @@ -403,6 +400,37 @@ async fn virtual_tcp_interface( { let mut client_socket = socket_set.get::(client_handle); + if !shutdown && client_socket.state() == TcpState::Closed && !has_connected { + // Not shutting down, but the client socket is closed, and the client never successfully connected. + if connection_attempts < 10 { + // Try to connect + client_socket + .connect( + (IpAddress::from(dest_addr.ip()), dest_addr.port()), + (IpAddress::from(source_peer_ip), virtual_port), + ) + .with_context(|| "Virtual server socket failed to listen")?; + if connection_attempts > 0 { + debug!( + "[{}] Virtual client retrying connection in 500ms", + virtual_port + ); + // Not our first connection attempt, wait a little bit. + tokio::time::sleep(Duration::from_millis(500)).await; + } + } else { + // Too many connection attempts + abort.store(true, Ordering::Relaxed); + } + connection_attempts += 1; + continue; + } + + if client_socket.state() == TcpState::Established { + // Prevent reconnection if the server later closes. + has_connected = true; + } + if client_socket.can_recv() { match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { Ok(data) => { From 070c0f516242f805232ece780b08846163d324fa Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 18 Oct 2021 22:13:13 -0400 Subject: [PATCH 019/165] Use Vec instead of static mut for socket storage. Update smoltcp to fix #17 --- Cargo.lock | 3 ++- src/main.rs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edcdee8..e87a015 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -864,13 +864,14 @@ checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smoltcp" version = "0.8.0" -source = "git+https://github.com/smoltcp-rs/smoltcp?branch=master#35e833e33dfd3e4efc3eb7d5de06bec17c54b011" +source = "git+https://github.com/smoltcp-rs/smoltcp?branch=master#25c539bb7c96789270f032ede2a967cf0fe5cf57" dependencies = [ "bitflags", "byteorder", "libc", "log", "managed", + "rand_core", ] [[package]] diff --git a/src/main.rs b/src/main.rs index bd28f96..a117a99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -347,10 +347,10 @@ async fn virtual_tcp_interface( }; let client_socket: anyhow::Result = { - static mut TCP_SERVER_RX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; - static mut TCP_SERVER_TX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; - let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); - let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + let rx_data = vec![0u8; MAX_PACKET]; + let tx_data = vec![0u8; MAX_PACKET]; + let tcp_rx_buffer = TcpSocketBuffer::new(rx_data); + let tcp_tx_buffer = TcpSocketBuffer::new(tx_data); let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); Ok(socket) }; From c2d0b9719a2264ae6231dd52c53b030c7a5c1076 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 19 Oct 2021 00:43:59 -0400 Subject: [PATCH 020/165] Refactor TCP virtual interface code out of main. Removed unused server socket buffer. --- Cargo.lock | 12 ++ Cargo.toml | 1 + src/main.rs | 278 ++++----------------------------------- src/virtual_iface/mod.rs | 10 ++ src/virtual_iface/tcp.rs | 274 ++++++++++++++++++++++++++++++++++++++ src/wg.rs | 2 +- 6 files changed, 322 insertions(+), 255 deletions(-) create mode 100644 src/virtual_iface/mod.rs create mode 100644 src/virtual_iface/tcp.rs diff --git a/Cargo.lock b/Cargo.lock index e87a015..f3ba00d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,17 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" +[[package]] +name = "async-trait" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -595,6 +606,7 @@ name = "onetun" version = "0.1.11" dependencies = [ "anyhow", + "async-trait", "boringtun", "clap", "futures", diff --git a/Cargo.toml b/Cargo.toml index a6f927f..d552c2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ lockfree = "0.5.1" futures = "0.3.17" rand = "0.8.4" nom = "7" +async-trait = "0.1.51" diff --git a/src/main.rs b/src/main.rs index a117a99..28846fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,24 @@ #[macro_use] extern crate log; -use std::net::{IpAddr, SocketAddr}; +use std::net::IpAddr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::time::Duration; use anyhow::Context; -use smoltcp::iface::InterfaceBuilder; -use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer, TcpState}; -use smoltcp::wire::{IpAddress, IpCidr}; use tokio::net::{TcpListener, TcpStream}; use crate::config::{Config, PortForwardConfig, PortProtocol}; use crate::port_pool::PortPool; -use crate::virtual_device::VirtualIpDevice; +use crate::virtual_iface::tcp::TcpVirtualInterface; +use crate::virtual_iface::VirtualInterfacePoll; use crate::wg::WireGuardTunnel; pub mod config; pub mod ip_sink; pub mod port_pool; pub mod virtual_device; +pub mod virtual_iface; pub mod wg; pub const MAX_PACKET: usize = 65536; @@ -92,29 +90,18 @@ async fn port_forward( ); match port_forward.protocol { - PortProtocol::Tcp => { - tcp_proxy_server( - port_forward.source, - port_forward.destination, - source_peer_ip, - port_pool, - wg, - ) - .await - } + PortProtocol::Tcp => tcp_proxy_server(port_forward, port_pool, wg).await, PortProtocol::Udp => Err(anyhow::anyhow!("UDP isn't supported just yet.")), } } /// Starts the server that listens on TCP connections. async fn tcp_proxy_server( - listen_addr: SocketAddr, - dest_addr: SocketAddr, - source_peer_ip: IpAddr, + port_forward: PortForwardConfig, port_pool: Arc, wg: Arc, ) -> anyhow::Result<()> { - let listener = TcpListener::bind(listen_addr) + let listener = TcpListener::bind(port_forward.source) .await .with_context(|| "Failed to listen on TCP proxy server")?; @@ -144,14 +131,8 @@ async fn tcp_proxy_server( tokio::spawn(async move { let port_pool = Arc::clone(&port_pool); - let result = handle_tcp_proxy_connection( - socket, - virtual_port, - source_peer_ip, - dest_addr, - wg.clone(), - ) - .await; + let result = + handle_tcp_proxy_connection(socket, virtual_port, port_forward, wg.clone()).await; if let Err(e) = result { error!( @@ -173,8 +154,7 @@ async fn tcp_proxy_server( async fn handle_tcp_proxy_connection( socket: TcpStream, virtual_port: u16, - source_peer_ip: IpAddr, - dest_addr: SocketAddr, + port_forward: PortForwardConfig, wg: Arc, ) -> anyhow::Result<()> { // Abort signal for stopping the Virtual Interface @@ -194,18 +174,21 @@ async fn handle_tcp_proxy_connection( // Spawn virtual interface { let abort = abort.clone(); + let virtual_interface = TcpVirtualInterface::new( + virtual_port, + port_forward, + wg, + abort.clone(), + data_to_real_client_tx, + data_to_virtual_server_rx, + virtual_client_ready_tx, + ); + tokio::spawn(async move { - virtual_tcp_interface( - virtual_port, - source_peer_ip, - dest_addr, - wg, - abort, - data_to_real_client_tx, - data_to_virtual_server_rx, - virtual_client_ready_tx, - ) - .await + virtual_interface.poll_loop().await.unwrap_or_else(|e| { + error!("Virtual interface poll loop failed unexpectedly: {}", e); + abort.store(true, Ordering::Relaxed); + }) }); } @@ -297,219 +280,6 @@ async fn handle_tcp_proxy_connection( Ok(()) } -#[allow(clippy::too_many_arguments)] -async fn virtual_tcp_interface( - virtual_port: u16, - source_peer_ip: IpAddr, - dest_addr: SocketAddr, - wg: Arc, - abort: Arc, - data_to_real_client_tx: tokio::sync::mpsc::Sender>, - mut data_to_virtual_server_rx: tokio::sync::mpsc::Receiver>, - virtual_client_ready_tx: tokio::sync::oneshot::Sender<()>, -) -> anyhow::Result<()> { - let mut virtual_client_ready_tx = Some(virtual_client_ready_tx); - - // Create a device and interface to simulate IP packets - // In essence: - // * TCP packets received from the 'real' client are 'sent' to the 'virtual server' via the 'virtual client' - // * Those TCP packets generate IP packets, which are captured from the interface and sent to the WireGuardTunnel - // * IP packets received by the WireGuardTunnel (from the endpoint) are fed into this 'virtual interface' - // * The interface processes those IP packets and routes them to the 'virtual client' (the rest is discarded) - // * The TCP data read by the 'virtual client' is sent to the 'real' TCP client - - // Consumer for IP packets to send through the virtual interface - // Initialize the interface - let device = VirtualIpDevice::new(virtual_port, wg) - .with_context(|| "Failed to initialize VirtualIpDevice")?; - let mut virtual_interface = InterfaceBuilder::new(device) - .ip_addrs([ - // Interface handles IP packets for the sender and recipient - IpCidr::new(IpAddress::from(source_peer_ip), 32), - IpCidr::new(IpAddress::from(dest_addr.ip()), 32), - ]) - .finalize(); - - // Server socket: this is a placeholder for the interface to route new connections to. - // TODO: Determine if we even need buffers here. - let server_socket: anyhow::Result = { - static mut TCP_SERVER_RX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; - static mut TCP_SERVER_TX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; - let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); - let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); - let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - - socket - .listen((IpAddress::from(dest_addr.ip()), dest_addr.port())) - .with_context(|| "Virtual server socket failed to listen")?; - - Ok(socket) - }; - - let client_socket: anyhow::Result = { - let rx_data = vec![0u8; MAX_PACKET]; - let tx_data = vec![0u8; MAX_PACKET]; - let tcp_rx_buffer = TcpSocketBuffer::new(rx_data); - let tcp_tx_buffer = TcpSocketBuffer::new(tx_data); - let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - Ok(socket) - }; - - // Socket set: there are always 2 sockets: 1 virtual client and 1 virtual server. - let mut socket_set_entries: [_; 2] = Default::default(); - let mut socket_set = SocketSet::new(&mut socket_set_entries[..]); - let _server_handle = socket_set.add(server_socket?); - let client_handle = socket_set.add(client_socket?); - - // Any data that wasn't sent because it was over the sending buffer limit - let mut tx_extra = Vec::new(); - - // Counts the connection attempts by the virtual client - let mut connection_attempts = 0; - // Whether the client has successfully connected before. Prevents the case of connecting again. - let mut has_connected = false; - - loop { - let loop_start = smoltcp::time::Instant::now(); - - // Shutdown occurs when the real client closes the connection, - // or if the client was in a CLOSE-WAIT state (after a server FIN) and had no data to send anymore. - // One last poll-loop iteration is executed so that the RST segment can be dispatched. - let shutdown = abort.load(Ordering::Relaxed); - - if shutdown { - // Shutdown: sends a RST packet. - trace!("[{}] Shutting down virtual interface", virtual_port); - let mut client_socket = socket_set.get::(client_handle); - client_socket.abort(); - } - - match virtual_interface.poll(&mut socket_set, loop_start) { - Ok(processed) if processed => { - trace!( - "[{}] Virtual interface polled some packets to be processed", - virtual_port - ); - } - Err(e) => { - error!("[{}] Virtual interface poll error: {:?}", virtual_port, e); - } - _ => {} - } - - { - let mut client_socket = socket_set.get::(client_handle); - - if !shutdown && client_socket.state() == TcpState::Closed && !has_connected { - // Not shutting down, but the client socket is closed, and the client never successfully connected. - if connection_attempts < 10 { - // Try to connect - client_socket - .connect( - (IpAddress::from(dest_addr.ip()), dest_addr.port()), - (IpAddress::from(source_peer_ip), virtual_port), - ) - .with_context(|| "Virtual server socket failed to listen")?; - if connection_attempts > 0 { - debug!( - "[{}] Virtual client retrying connection in 500ms", - virtual_port - ); - // Not our first connection attempt, wait a little bit. - tokio::time::sleep(Duration::from_millis(500)).await; - } - } else { - // Too many connection attempts - abort.store(true, Ordering::Relaxed); - } - connection_attempts += 1; - continue; - } - - if client_socket.state() == TcpState::Established { - // Prevent reconnection if the server later closes. - has_connected = true; - } - - if client_socket.can_recv() { - match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { - Ok(data) => { - trace!( - "[{}] Virtual client received {} bytes of data", - virtual_port, - data.len() - ); - // Send it to the real client - if let Err(e) = data_to_real_client_tx.send(data).await { - error!("[{}] Failed to dispatch data from virtual client to real client: {:?}", virtual_port, e); - } - } - Err(e) => { - error!( - "[{}] Failed to read from virtual client socket: {:?}", - virtual_port, e - ); - } - } - } - if client_socket.can_send() { - if let Some(virtual_client_ready_tx) = virtual_client_ready_tx.take() { - virtual_client_ready_tx - .send(()) - .expect("Failed to notify real client that virtual client is ready"); - } - - let mut to_transfer = None; - - if tx_extra.is_empty() { - // The payload segment from the previous loop is complete, - // we can now read the next payload in the queue. - if let Ok(data) = data_to_virtual_server_rx.try_recv() { - to_transfer = Some(data); - } else if client_socket.state() == TcpState::CloseWait { - // No data to be sent in this loop. If the client state is CLOSE-WAIT (because of a server FIN), - // the interface is shutdown. - trace!("[{}] Shutting down virtual interface because client sent no more data, and server sent FIN (CLOSE-WAIT)", virtual_port); - abort.store(true, Ordering::Relaxed); - continue; - } - } - - let to_transfer_slice = to_transfer.as_ref().unwrap_or(&tx_extra).as_slice(); - if !to_transfer_slice.is_empty() { - let total = to_transfer_slice.len(); - match client_socket.send_slice(to_transfer_slice) { - Ok(sent) => { - trace!( - "[{}] Sent {}/{} bytes via virtual client socket", - virtual_port, - sent, - total, - ); - tx_extra = Vec::from(&to_transfer_slice[sent..total]); - } - Err(e) => { - error!( - "[{}] Failed to send slice via virtual client socket: {:?}", - virtual_port, e - ); - } - } - } - } - } - - if shutdown { - break; - } - - tokio::time::sleep(Duration::from_millis(1)).await; - } - trace!("[{}] Virtual interface task terminated", virtual_port); - abort.store(true, Ordering::Relaxed); - Ok(()) -} - fn init_logger(config: &Config) -> anyhow::Result<()> { let mut builder = pretty_env_logger::formatted_builder(); builder.parse_filters(&config.log); diff --git a/src/virtual_iface/mod.rs b/src/virtual_iface/mod.rs new file mode 100644 index 0000000..b9d3354 --- /dev/null +++ b/src/virtual_iface/mod.rs @@ -0,0 +1,10 @@ +pub mod tcp; + +use async_trait::async_trait; + +#[async_trait] +pub trait VirtualInterfacePoll { + /// Initializes the virtual interface and processes incoming data to be dispatched + /// to the WireGuard tunnel and to the real client. + async fn poll_loop(mut self) -> anyhow::Result<()>; +} diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs new file mode 100644 index 0000000..c9fee95 --- /dev/null +++ b/src/virtual_iface/tcp.rs @@ -0,0 +1,274 @@ +use crate::config::PortForwardConfig; +use crate::virtual_device::VirtualIpDevice; +use crate::virtual_iface::VirtualInterfacePoll; +use crate::wg::WireGuardTunnel; +use anyhow::Context; +use async_trait::async_trait; +use smoltcp::iface::InterfaceBuilder; +use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer, TcpState}; +use smoltcp::wire::{IpAddress, IpCidr}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +const MAX_PACKET: usize = 65536; + +/// A virtual interface for proxying Layer 7 data to Layer 3 packets, and vice-versa. +pub struct TcpVirtualInterface { + /// The virtual port assigned to the virtual client, used to + /// route Layer 4 segments/datagrams to and from the WireGuard tunnel. + virtual_port: u16, + /// The overall port-forward configuration: used for the destination address (on which + /// the virtual server listens) and the protocol in use. + port_forward: PortForwardConfig, + /// The WireGuard tunnel to send IP packets to. + wg: Arc, + /// Abort signal to shutdown the virtual interface and its parent task. + abort: Arc, + /// Channel sender for pushing Layer 7 data back to the real client. + data_to_real_client_tx: tokio::sync::mpsc::Sender>, + /// Channel receiver for processing Layer 7 data through the virtual interface. + data_to_virtual_server_rx: tokio::sync::mpsc::Receiver>, + /// One-shot sender to notify the parent task that the virtual client is ready to send Layer 7 data. + virtual_client_ready_tx: tokio::sync::oneshot::Sender<()>, +} + +impl TcpVirtualInterface { + /// Initialize the parameters for a new virtual interface. + /// Use the `poll_loop()` future to start the virtual interface poll loop. + pub fn new( + virtual_port: u16, + port_forward: PortForwardConfig, + wg: Arc, + abort: Arc, + data_to_real_client_tx: tokio::sync::mpsc::Sender>, + data_to_virtual_server_rx: tokio::sync::mpsc::Receiver>, + virtual_client_ready_tx: tokio::sync::oneshot::Sender<()>, + ) -> Self { + Self { + virtual_port, + port_forward, + wg, + abort, + data_to_real_client_tx, + data_to_virtual_server_rx, + virtual_client_ready_tx, + } + } +} + +#[async_trait] +impl VirtualInterfacePoll for TcpVirtualInterface { + async fn poll_loop(self) -> anyhow::Result<()> { + let mut virtual_client_ready_tx = Some(self.virtual_client_ready_tx); + let mut data_to_virtual_server_rx = self.data_to_virtual_server_rx; + let source_peer_ip = self.wg.source_peer_ip; + + // Create a device and interface to simulate IP packets + // In essence: + // * TCP packets received from the 'real' client are 'sent' to the 'virtual server' via the 'virtual client' + // * Those TCP packets generate IP packets, which are captured from the interface and sent to the WireGuardTunnel + // * IP packets received by the WireGuardTunnel (from the endpoint) are fed into this 'virtual interface' + // * The interface processes those IP packets and routes them to the 'virtual client' (the rest is discarded) + // * The TCP data read by the 'virtual client' is sent to the 'real' TCP client + + // Consumer for IP packets to send through the virtual interface + // Initialize the interface + let device = VirtualIpDevice::new(self.virtual_port, self.wg) + .with_context(|| "Failed to initialize TCP VirtualIpDevice")?; + let mut virtual_interface = InterfaceBuilder::new(device) + .ip_addrs([ + // Interface handles IP packets for the sender and recipient + IpCidr::new(IpAddress::from(source_peer_ip), 32), + IpCidr::new(IpAddress::from(self.port_forward.destination.ip()), 32), + ]) + .finalize(); + + // Server socket: this is a placeholder for the interface to route new connections to. + let server_socket: anyhow::Result = { + static mut TCP_SERVER_RX_DATA: [u8; 0] = []; + static mut TCP_SERVER_TX_DATA: [u8; 0] = []; + let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); + let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + + socket + .listen(( + IpAddress::from(self.port_forward.destination.ip()), + self.port_forward.destination.port(), + )) + .with_context(|| "Virtual server socket failed to listen")?; + + Ok(socket) + }; + + let client_socket: anyhow::Result = { + let rx_data = vec![0u8; MAX_PACKET]; + let tx_data = vec![0u8; MAX_PACKET]; + let tcp_rx_buffer = TcpSocketBuffer::new(rx_data); + let tcp_tx_buffer = TcpSocketBuffer::new(tx_data); + let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + Ok(socket) + }; + + // Socket set: there are always 2 sockets: 1 virtual client and 1 virtual server. + let mut socket_set_entries: [_; 2] = Default::default(); + let mut socket_set = SocketSet::new(&mut socket_set_entries[..]); + let _server_handle = socket_set.add(server_socket?); + let client_handle = socket_set.add(client_socket?); + + // Any data that wasn't sent because it was over the sending buffer limit + let mut tx_extra = Vec::new(); + + // Counts the connection attempts by the virtual client + let mut connection_attempts = 0; + // Whether the client has successfully connected before. Prevents the case of connecting again. + let mut has_connected = false; + + loop { + let loop_start = smoltcp::time::Instant::now(); + + // Shutdown occurs when the real client closes the connection, + // or if the client was in a CLOSE-WAIT state (after a server FIN) and had no data to send anymore. + // One last poll-loop iteration is executed so that the RST segment can be dispatched. + let shutdown = self.abort.load(Ordering::Relaxed); + + if shutdown { + // Shutdown: sends a RST packet. + trace!("[{}] Shutting down virtual interface", self.virtual_port); + let mut client_socket = socket_set.get::(client_handle); + client_socket.abort(); + } + + match virtual_interface.poll(&mut socket_set, loop_start) { + Ok(processed) if processed => { + trace!( + "[{}] Virtual interface polled some packets to be processed", + self.virtual_port + ); + } + Err(e) => { + error!( + "[{}] Virtual interface poll error: {:?}", + self.virtual_port, e + ); + } + _ => {} + } + + { + let mut client_socket = socket_set.get::(client_handle); + + if !shutdown && client_socket.state() == TcpState::Closed && !has_connected { + // Not shutting down, but the client socket is closed, and the client never successfully connected. + if connection_attempts < 10 { + // Try to connect + client_socket + .connect( + ( + IpAddress::from(self.port_forward.destination.ip()), + self.port_forward.destination.port(), + ), + (IpAddress::from(source_peer_ip), self.virtual_port), + ) + .with_context(|| "Virtual server socket failed to listen")?; + if connection_attempts > 0 { + debug!( + "[{}] Virtual client retrying connection in 500ms", + self.virtual_port + ); + // Not our first connection attempt, wait a little bit. + tokio::time::sleep(Duration::from_millis(500)).await; + } + } else { + // Too many connection attempts + self.abort.store(true, Ordering::Relaxed); + } + connection_attempts += 1; + continue; + } + + if client_socket.state() == TcpState::Established { + // Prevent reconnection if the server later closes. + has_connected = true; + } + + if client_socket.can_recv() { + match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { + Ok(data) => { + trace!( + "[{}] Virtual client received {} bytes of data", + self.virtual_port, + data.len() + ); + // Send it to the real client + if let Err(e) = self.data_to_real_client_tx.send(data).await { + error!("[{}] Failed to dispatch data from virtual client to real client: {:?}", self.virtual_port, e); + } + } + Err(e) => { + error!( + "[{}] Failed to read from virtual client socket: {:?}", + self.virtual_port, e + ); + } + } + } + if client_socket.can_send() { + if let Some(virtual_client_ready_tx) = virtual_client_ready_tx.take() { + virtual_client_ready_tx + .send(()) + .expect("Failed to notify real client that virtual client is ready"); + } + + let mut to_transfer = None; + + if tx_extra.is_empty() { + // The payload segment from the previous loop is complete, + // we can now read the next payload in the queue. + if let Ok(data) = data_to_virtual_server_rx.try_recv() { + to_transfer = Some(data); + } else if client_socket.state() == TcpState::CloseWait { + // No data to be sent in this loop. If the client state is CLOSE-WAIT (because of a server FIN), + // the interface is shutdown. + trace!("[{}] Shutting down virtual interface because client sent no more data, and server sent FIN (CLOSE-WAIT)", self.virtual_port); + self.abort.store(true, Ordering::Relaxed); + continue; + } + } + + let to_transfer_slice = to_transfer.as_ref().unwrap_or(&tx_extra).as_slice(); + if !to_transfer_slice.is_empty() { + let total = to_transfer_slice.len(); + match client_socket.send_slice(to_transfer_slice) { + Ok(sent) => { + trace!( + "[{}] Sent {}/{} bytes via virtual client socket", + self.virtual_port, + sent, + total, + ); + tx_extra = Vec::from(&to_transfer_slice[sent..total]); + } + Err(e) => { + error!( + "[{}] Failed to send slice via virtual client socket: {:?}", + self.virtual_port, e + ); + } + } + } + } + } + + if shutdown { + break; + } + + tokio::time::sleep(Duration::from_millis(1)).await; + } + trace!("[{}] Virtual interface task terminated", self.virtual_port); + self.abort.store(true, Ordering::Relaxed); + Ok(()) + } +} diff --git a/src/wg.rs b/src/wg.rs index e2740e8..3d3cc5f 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -18,7 +18,7 @@ const DISPATCH_CAPACITY: usize = 1_000; /// to be sent to and received from a remote UDP endpoint. /// This tunnel supports at most 1 peer IP at a time, but supports simultaneous ports. pub struct WireGuardTunnel { - source_peer_ip: IpAddr, + pub(crate) source_peer_ip: IpAddr, /// `boringtun` peer/tunnel implementation, used for crypto & WG protocol. peer: Box, /// The UDP socket for the public WireGuard endpoint to connect to. From 703f2613449c6817bac00eadae61d7519e1802ca Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 19 Oct 2021 01:00:05 -0400 Subject: [PATCH 021/165] Move TCP tunneling code to separate module --- README.md | 2 +- src/main.rs | 218 +--------------------------------------------- src/tunnel/mod.rs | 29 ++++++ src/tunnel/tcp.rs | 196 +++++++++++++++++++++++++++++++++++++++++ src/wg.rs | 2 +- 5 files changed, 230 insertions(+), 217 deletions(-) create mode 100644 src/tunnel/mod.rs create mode 100644 src/tunnel/tcp.rs diff --git a/README.md b/README.md index f652574..1defff3 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ local port, say `127.0.0.1:8080`, that will tunnel through WireGuard to reach th You'll then see this log: ``` -INFO onetun > Tunnelling [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +INFO onetun > Tunneling [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` Which means you can now access the port locally! diff --git a/src/main.rs b/src/main.rs index 28846fa..b5da3ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,22 @@ #[macro_use] extern crate log; -use std::net::IpAddr; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use anyhow::Context; -use tokio::net::{TcpListener, TcpStream}; -use crate::config::{Config, PortForwardConfig, PortProtocol}; +use crate::config::Config; use crate::port_pool::PortPool; -use crate::virtual_iface::tcp::TcpVirtualInterface; -use crate::virtual_iface::VirtualInterfacePoll; use crate::wg::WireGuardTunnel; pub mod config; pub mod ip_sink; pub mod port_pool; +pub mod tunnel; pub mod virtual_device; pub mod virtual_iface; pub mod wg; -pub const MAX_PACKET: usize = 65536; - #[tokio::main] async fn main() -> anyhow::Result<()> { let config = Config::from_args().with_context(|| "Failed to read config")?; @@ -63,7 +57,7 @@ async fn main() -> anyhow::Result<()> { std::thread::spawn(move || { let cpu_pool = tokio::runtime::Runtime::new().unwrap(); cpu_pool.block_on(async move { - port_forward(pf, source_peer_ip, port_pool, wg) + tunnel::port_forward(pf, source_peer_ip, port_pool, wg) .await .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) }); @@ -74,212 +68,6 @@ async fn main() -> anyhow::Result<()> { futures::future::pending().await } -async fn port_forward( - port_forward: PortForwardConfig, - source_peer_ip: IpAddr, - port_pool: Arc, - wg: Arc, -) -> anyhow::Result<()> { - info!( - "Tunnelling {} [{}]->[{}] (via [{}] as peer {})", - port_forward.protocol, - port_forward.source, - port_forward.destination, - &wg.endpoint, - source_peer_ip - ); - - match port_forward.protocol { - PortProtocol::Tcp => tcp_proxy_server(port_forward, port_pool, wg).await, - PortProtocol::Udp => Err(anyhow::anyhow!("UDP isn't supported just yet.")), - } -} - -/// Starts the server that listens on TCP connections. -async fn tcp_proxy_server( - port_forward: PortForwardConfig, - port_pool: Arc, - wg: Arc, -) -> anyhow::Result<()> { - let listener = TcpListener::bind(port_forward.source) - .await - .with_context(|| "Failed to listen on TCP proxy server")?; - - loop { - let wg = wg.clone(); - let port_pool = port_pool.clone(); - let (socket, peer_addr) = listener - .accept() - .await - .with_context(|| "Failed to accept connection on TCP proxy server")?; - - // Assign a 'virtual port': this is a unique port number used to route IP packets - // received from the WireGuard tunnel. It is the port number that the virtual client will - // listen on. - let virtual_port = match port_pool.next() { - Ok(port) => port, - Err(e) => { - error!( - "Failed to assign virtual port number for connection [{}]: {:?}", - peer_addr, e - ); - continue; - } - }; - - info!("[{}] Incoming connection from {}", virtual_port, peer_addr); - - tokio::spawn(async move { - let port_pool = Arc::clone(&port_pool); - let result = - handle_tcp_proxy_connection(socket, virtual_port, port_forward, wg.clone()).await; - - if let Err(e) = result { - error!( - "[{}] Connection dropped un-gracefully: {:?}", - virtual_port, e - ); - } else { - info!("[{}] Connection closed by client", virtual_port); - } - - // Release port when connection drops - wg.release_virtual_interface(virtual_port); - port_pool.release(virtual_port); - }); - } -} - -/// Handles a new TCP connection with its assigned virtual port. -async fn handle_tcp_proxy_connection( - socket: TcpStream, - virtual_port: u16, - port_forward: PortForwardConfig, - wg: Arc, -) -> anyhow::Result<()> { - // Abort signal for stopping the Virtual Interface - let abort = Arc::new(AtomicBool::new(false)); - - // Signals that the Virtual Client is ready to send data - let (virtual_client_ready_tx, virtual_client_ready_rx) = tokio::sync::oneshot::channel::<()>(); - - // data_to_real_client_(tx/rx): This task reads the data from this mpsc channel to send back - // to the real client. - let (data_to_real_client_tx, mut data_to_real_client_rx) = tokio::sync::mpsc::channel(1_000); - - // data_to_real_server_(tx/rx): This task sends the data received from the real client to the - // virtual interface (virtual server socket). - let (data_to_virtual_server_tx, data_to_virtual_server_rx) = tokio::sync::mpsc::channel(1_000); - - // Spawn virtual interface - { - let abort = abort.clone(); - let virtual_interface = TcpVirtualInterface::new( - virtual_port, - port_forward, - wg, - abort.clone(), - data_to_real_client_tx, - data_to_virtual_server_rx, - virtual_client_ready_tx, - ); - - tokio::spawn(async move { - virtual_interface.poll_loop().await.unwrap_or_else(|e| { - error!("Virtual interface poll loop failed unexpectedly: {}", e); - abort.store(true, Ordering::Relaxed); - }) - }); - } - - // Wait for virtual client to be ready. - virtual_client_ready_rx - .await - .with_context(|| "Virtual client dropped before being ready.")?; - trace!("[{}] Virtual client is ready to send data", virtual_port); - - loop { - tokio::select! { - readable_result = socket.readable() => { - match readable_result { - Ok(_) => { - // Buffer for the individual TCP segment. - let mut buffer = Vec::with_capacity(MAX_PACKET); - match socket.try_read_buf(&mut buffer) { - Ok(size) if size > 0 => { - let data = &buffer[..size]; - debug!( - "[{}] Read {} bytes of TCP data from real client", - virtual_port, size - ); - if let Err(e) = data_to_virtual_server_tx.send(data.to_vec()).await { - error!( - "[{}] Failed to dispatch data to virtual interface: {:?}", - virtual_port, e - ); - } - } - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - continue; - } - Err(e) => { - error!( - "[{}] Failed to read from client TCP socket: {:?}", - virtual_port, e - ); - break; - } - _ => { - break; - } - } - } - Err(e) => { - error!("[{}] Failed to check if readable: {:?}", virtual_port, e); - break; - } - } - } - data_recv_result = data_to_real_client_rx.recv() => { - match data_recv_result { - Some(data) => match socket.try_write(&data) { - Ok(size) => { - debug!( - "[{}] Wrote {} bytes of TCP data to real client", - virtual_port, size - ); - } - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - if abort.load(Ordering::Relaxed) { - break; - } else { - continue; - } - } - Err(e) => { - error!( - "[{}] Failed to write to client TCP socket: {:?}", - virtual_port, e - ); - } - }, - None => { - if abort.load(Ordering::Relaxed) { - break; - } else { - continue; - } - }, - } - } - } - } - - trace!("[{}] TCP socket handler task terminated", virtual_port); - abort.store(true, Ordering::Relaxed); - Ok(()) -} - fn init_logger(config: &Config) -> anyhow::Result<()> { let mut builder = pretty_env_logger::formatted_builder(); builder.parse_filters(&config.log); diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs new file mode 100644 index 0000000..7eab856 --- /dev/null +++ b/src/tunnel/mod.rs @@ -0,0 +1,29 @@ +use std::net::IpAddr; +use std::sync::Arc; + +use crate::config::{PortForwardConfig, PortProtocol}; +use crate::port_pool::PortPool; +use crate::wg::WireGuardTunnel; + +mod tcp; + +pub async fn port_forward( + port_forward: PortForwardConfig, + source_peer_ip: IpAddr, + port_pool: Arc, + wg: Arc, +) -> anyhow::Result<()> { + info!( + "Tunneling {} [{}]->[{}] (via [{}] as peer {})", + port_forward.protocol, + port_forward.source, + port_forward.destination, + &wg.endpoint, + source_peer_ip + ); + + match port_forward.protocol { + PortProtocol::Tcp => tcp::tcp_proxy_server(port_forward, port_pool, wg).await, + PortProtocol::Udp => Err(anyhow::anyhow!("UDP isn't supported just yet.")), + } +} diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs new file mode 100644 index 0000000..987aa5e --- /dev/null +++ b/src/tunnel/tcp.rs @@ -0,0 +1,196 @@ +use crate::config::PortForwardConfig; +use crate::port_pool::PortPool; +use crate::virtual_iface::tcp::TcpVirtualInterface; +use crate::virtual_iface::VirtualInterfacePoll; +use crate::wg::WireGuardTunnel; +use anyhow::Context; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use tokio::net::{TcpListener, TcpStream}; + +const MAX_PACKET: usize = 65536; + +/// Starts the server that listens on TCP connections. +pub async fn tcp_proxy_server( + port_forward: PortForwardConfig, + port_pool: Arc, + wg: Arc, +) -> anyhow::Result<()> { + let listener = TcpListener::bind(port_forward.source) + .await + .with_context(|| "Failed to listen on TCP proxy server")?; + + loop { + let wg = wg.clone(); + let port_pool = port_pool.clone(); + let (socket, peer_addr) = listener + .accept() + .await + .with_context(|| "Failed to accept connection on TCP proxy server")?; + + // Assign a 'virtual port': this is a unique port number used to route IP packets + // received from the WireGuard tunnel. It is the port number that the virtual client will + // listen on. + let virtual_port = match port_pool.next() { + Ok(port) => port, + Err(e) => { + error!( + "Failed to assign virtual port number for connection [{}]: {:?}", + peer_addr, e + ); + continue; + } + }; + + info!("[{}] Incoming connection from {}", virtual_port, peer_addr); + + tokio::spawn(async move { + let port_pool = Arc::clone(&port_pool); + let result = + handle_tcp_proxy_connection(socket, virtual_port, port_forward, wg.clone()).await; + + if let Err(e) = result { + error!( + "[{}] Connection dropped un-gracefully: {:?}", + virtual_port, e + ); + } else { + info!("[{}] Connection closed by client", virtual_port); + } + + // Release port when connection drops + wg.release_virtual_interface(virtual_port); + port_pool.release(virtual_port); + }); + } +} + +/// Handles a new TCP connection with its assigned virtual port. +async fn handle_tcp_proxy_connection( + socket: TcpStream, + virtual_port: u16, + port_forward: PortForwardConfig, + wg: Arc, +) -> anyhow::Result<()> { + // Abort signal for stopping the Virtual Interface + let abort = Arc::new(AtomicBool::new(false)); + + // Signals that the Virtual Client is ready to send data + let (virtual_client_ready_tx, virtual_client_ready_rx) = tokio::sync::oneshot::channel::<()>(); + + // data_to_real_client_(tx/rx): This task reads the data from this mpsc channel to send back + // to the real client. + let (data_to_real_client_tx, mut data_to_real_client_rx) = tokio::sync::mpsc::channel(1_000); + + // data_to_real_server_(tx/rx): This task sends the data received from the real client to the + // virtual interface (virtual server socket). + let (data_to_virtual_server_tx, data_to_virtual_server_rx) = tokio::sync::mpsc::channel(1_000); + + // Spawn virtual interface + { + let abort = abort.clone(); + let virtual_interface = TcpVirtualInterface::new( + virtual_port, + port_forward, + wg, + abort.clone(), + data_to_real_client_tx, + data_to_virtual_server_rx, + virtual_client_ready_tx, + ); + + tokio::spawn(async move { + virtual_interface.poll_loop().await.unwrap_or_else(|e| { + error!("Virtual interface poll loop failed unexpectedly: {}", e); + abort.store(true, Ordering::Relaxed); + }) + }); + } + + // Wait for virtual client to be ready. + virtual_client_ready_rx + .await + .with_context(|| "Virtual client dropped before being ready.")?; + trace!("[{}] Virtual client is ready to send data", virtual_port); + + loop { + tokio::select! { + readable_result = socket.readable() => { + match readable_result { + Ok(_) => { + // Buffer for the individual TCP segment. + let mut buffer = Vec::with_capacity(MAX_PACKET); + match socket.try_read_buf(&mut buffer) { + Ok(size) if size > 0 => { + let data = &buffer[..size]; + debug!( + "[{}] Read {} bytes of TCP data from real client", + virtual_port, size + ); + if let Err(e) = data_to_virtual_server_tx.send(data.to_vec()).await { + error!( + "[{}] Failed to dispatch data to virtual interface: {:?}", + virtual_port, e + ); + } + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + continue; + } + Err(e) => { + error!( + "[{}] Failed to read from client TCP socket: {:?}", + virtual_port, e + ); + break; + } + _ => { + break; + } + } + } + Err(e) => { + error!("[{}] Failed to check if readable: {:?}", virtual_port, e); + break; + } + } + } + data_recv_result = data_to_real_client_rx.recv() => { + match data_recv_result { + Some(data) => match socket.try_write(&data) { + Ok(size) => { + debug!( + "[{}] Wrote {} bytes of TCP data to real client", + virtual_port, size + ); + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + if abort.load(Ordering::Relaxed) { + break; + } else { + continue; + } + } + Err(e) => { + error!( + "[{}] Failed to write to client TCP socket: {:?}", + virtual_port, e + ); + } + }, + None => { + if abort.load(Ordering::Relaxed) { + break; + } else { + continue; + } + }, + } + } + } + } + + trace!("[{}] TCP socket handler task terminated", virtual_port); + abort.store(true, Ordering::Relaxed); + Ok(()) +} diff --git a/src/wg.rs b/src/wg.rs index 3d3cc5f..498fb98 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -9,10 +9,10 @@ use tokio::net::UdpSocket; use tokio::sync::RwLock; use crate::config::Config; -use crate::MAX_PACKET; /// The capacity of the channel for received IP packets. const DISPATCH_CAPACITY: usize = 1_000; +const MAX_PACKET: usize = 65536; /// A WireGuard tunnel. Encapsulates and decapsulates IP packets /// to be sent to and received from a remote UDP endpoint. From 5cec6d4943557541fa5eff0afef4022e87987b3b Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 19 Oct 2021 01:55:04 -0400 Subject: [PATCH 022/165] Index ports with protocol in WG. Start writing UDP tunnel code with plans. --- src/config.rs | 2 +- src/main.rs | 14 ++++---- src/port_pool.rs | 62 ----------------------------------- src/tunnel/mod.rs | 12 ++++--- src/tunnel/tcp.rs | 70 +++++++++++++++++++++++++++++++++++++--- src/tunnel/udp.rs | 44 +++++++++++++++++++++++++ src/virtual_device.rs | 3 +- src/virtual_iface/mod.rs | 12 +++++++ src/virtual_iface/tcp.rs | 9 +++--- src/wg.rs | 19 +++++++---- 10 files changed, 156 insertions(+), 91 deletions(-) delete mode 100644 src/port_pool.rs create mode 100644 src/tunnel/udp.rs diff --git a/src/config.rs b/src/config.rs index 3a46881..0e3a727 100644 --- a/src/config.rs +++ b/src/config.rs @@ -332,7 +332,7 @@ impl Display for PortForwardConfig { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum PortProtocol { Tcp, Udp, diff --git a/src/main.rs b/src/main.rs index b5da3ad..0d301d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,11 @@ use std::sync::Arc; use anyhow::Context; use crate::config::Config; -use crate::port_pool::PortPool; +use crate::tunnel::tcp::TcpPortPool; use crate::wg::WireGuardTunnel; pub mod config; pub mod ip_sink; -pub mod port_pool; pub mod tunnel; pub mod virtual_device; pub mod virtual_iface; @@ -21,7 +20,10 @@ pub mod wg; async fn main() -> anyhow::Result<()> { let config = Config::from_args().with_context(|| "Failed to read config")?; init_logger(&config)?; - let port_pool = Arc::new(PortPool::new()); + + // Initialize the port pool for each protocol + let tcp_port_pool = Arc::new(TcpPortPool::new()); + // TODO: udp_port_pool let wg = WireGuardTunnel::new(&config) .await @@ -52,12 +54,12 @@ async fn main() -> anyhow::Result<()> { port_forwards .into_iter() - .map(|pf| (pf, wg.clone(), port_pool.clone())) - .for_each(move |(pf, wg, port_pool)| { + .map(|pf| (pf, wg.clone(), tcp_port_pool.clone())) + .for_each(move |(pf, wg, tcp_port_pool)| { std::thread::spawn(move || { let cpu_pool = tokio::runtime::Runtime::new().unwrap(); cpu_pool.block_on(async move { - tunnel::port_forward(pf, source_peer_ip, port_pool, wg) + tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, wg) .await .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) }); diff --git a/src/port_pool.rs b/src/port_pool.rs deleted file mode 100644 index 7bff712..0000000 --- a/src/port_pool.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::ops::Range; - -use anyhow::Context; -use rand::seq::SliceRandom; -use rand::thread_rng; - -const MIN_PORT: u16 = 32768; -const MAX_PORT: u16 = 60999; -const PORT_RANGE: Range = MIN_PORT..MAX_PORT; - -/// A pool of virtual ports available. -/// This structure is thread-safe and lock-free; you can use it safely in an `Arc`. -pub struct PortPool { - /// Remaining ports - inner: lockfree::queue::Queue, - /// Ports in use, with their associated IP channel sender. - taken: lockfree::set::Set, -} - -impl Default for PortPool { - fn default() -> Self { - Self::new() - } -} - -impl PortPool { - /// Initializes a new pool of virtual ports. - pub fn new() -> Self { - let inner = lockfree::queue::Queue::default(); - let mut ports: Vec = PORT_RANGE.collect(); - ports.shuffle(&mut thread_rng()); - ports.into_iter().for_each(|p| inner.push(p) as ()); - Self { - inner, - taken: lockfree::set::Set::new(), - } - } - - /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). - pub fn next(&self) -> anyhow::Result { - let port = self - .inner - .pop() - .with_context(|| "Virtual port pool is exhausted")?; - self.taken - .insert(port) - .ok() - .with_context(|| "Failed to insert taken")?; - Ok(port) - } - - /// Releases a port back into the pool. - pub fn release(&self, port: u16) { - self.inner.push(port); - self.taken.remove(&port); - } - - /// Whether the given port is in use by a virtual interface. - pub fn is_in_use(&self, port: u16) -> bool { - self.taken.contains(&port) - } -} diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index 7eab856..00fe4e8 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -2,15 +2,17 @@ use std::net::IpAddr; use std::sync::Arc; use crate::config::{PortForwardConfig, PortProtocol}; -use crate::port_pool::PortPool; +use crate::tunnel::tcp::TcpPortPool; use crate::wg::WireGuardTunnel; -mod tcp; +pub mod tcp; +#[allow(unused)] +pub mod udp; pub async fn port_forward( port_forward: PortForwardConfig, source_peer_ip: IpAddr, - port_pool: Arc, + tcp_port_pool: Arc, wg: Arc, ) -> anyhow::Result<()> { info!( @@ -23,7 +25,7 @@ pub async fn port_forward( ); match port_forward.protocol { - PortProtocol::Tcp => tcp::tcp_proxy_server(port_forward, port_pool, wg).await, - PortProtocol::Udp => Err(anyhow::anyhow!("UDP isn't supported just yet.")), + PortProtocol::Tcp => tcp::tcp_proxy_server(port_forward, tcp_port_pool, wg).await, + PortProtocol::Udp => udp::udp_proxy_server(port_forward, /* udp_port_pool, */ wg).await, } } diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index 987aa5e..1f01642 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -1,19 +1,26 @@ -use crate::config::PortForwardConfig; -use crate::port_pool::PortPool; +use crate::config::{PortForwardConfig, PortProtocol}; use crate::virtual_iface::tcp::TcpVirtualInterface; -use crate::virtual_iface::VirtualInterfacePoll; +use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::wg::WireGuardTunnel; use anyhow::Context; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::net::{TcpListener, TcpStream}; +use std::ops::Range; + +use rand::seq::SliceRandom; +use rand::thread_rng; + const MAX_PACKET: usize = 65536; +const MIN_PORT: u16 = 1000; +const MAX_PORT: u16 = 60999; +const PORT_RANGE: Range = MIN_PORT..MAX_PORT; /// Starts the server that listens on TCP connections. pub async fn tcp_proxy_server( port_forward: PortForwardConfig, - port_pool: Arc, + port_pool: Arc, wg: Arc, ) -> anyhow::Result<()> { let listener = TcpListener::bind(port_forward.source) @@ -59,7 +66,7 @@ pub async fn tcp_proxy_server( } // Release port when connection drops - wg.release_virtual_interface(virtual_port); + wg.release_virtual_interface(VirtualPort(virtual_port, PortProtocol::Tcp)); port_pool.release(virtual_port); }); } @@ -194,3 +201,56 @@ async fn handle_tcp_proxy_connection( abort.store(true, Ordering::Relaxed); Ok(()) } + +/// A pool of virtual ports available for TCP connections. +/// This structure is thread-safe and lock-free; you can use it safely in an `Arc`. +pub struct TcpPortPool { + /// Remaining ports + inner: lockfree::queue::Queue, + /// Ports in use, with their associated IP channel sender. + taken: lockfree::set::Set, +} + +impl Default for TcpPortPool { + fn default() -> Self { + Self::new() + } +} + +impl TcpPortPool { + /// Initializes a new pool of virtual ports. + pub fn new() -> Self { + let inner = lockfree::queue::Queue::default(); + let mut ports: Vec = PORT_RANGE.collect(); + ports.shuffle(&mut thread_rng()); + ports.into_iter().for_each(|p| inner.push(p) as ()); + Self { + inner, + taken: lockfree::set::Set::new(), + } + } + + /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). + pub fn next(&self) -> anyhow::Result { + let port = self + .inner + .pop() + .with_context(|| "Virtual port pool is exhausted")?; + self.taken + .insert(port) + .ok() + .with_context(|| "Failed to insert taken")?; + Ok(port) + } + + /// Releases a port back into the pool. + pub fn release(&self, port: u16) { + self.inner.push(port); + self.taken.remove(&port); + } + + /// Whether the given port is in use by a virtual interface. + pub fn is_in_use(&self, port: u16) -> bool { + self.taken.contains(&port) + } +} diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs new file mode 100644 index 0000000..7f9fda4 --- /dev/null +++ b/src/tunnel/udp.rs @@ -0,0 +1,44 @@ +use std::sync::Arc; + +use anyhow::Context; +use tokio::net::UdpSocket; + +use crate::config::PortForwardConfig; +use crate::wg::WireGuardTunnel; + +const MAX_PACKET: usize = 65536; + +/// How long to keep the UDP peer address assigned to its virtual specified port, in seconds. +const UDP_TIMEOUT_SECONDS: u64 = 60; + +/// To prevent port-flooding, we set a limit on the amount of open ports per IP address. +const PORTS_PER_IP: usize = 100; + +pub async fn udp_proxy_server( + port_forward: PortForwardConfig, + wg: Arc, +) -> anyhow::Result<()> { + let socket = UdpSocket::bind(port_forward.source) + .await + .with_context(|| "Failed to bind on UDP proxy address")?; + + let mut buffer = [0u8; MAX_PACKET]; + loop { + let (size, peer_addr) = socket + .recv_from(&mut buffer) + .await + .with_context(|| "Failed to accept incoming UDP datagram")?; + + let _wg = wg.clone(); + let _data = &buffer[..size].to_vec(); + debug!("Received datagram of {} bytes from {}", size, peer_addr); + + // Assign a 'virtual port': this is a unique port number used to route IP packets + // received from the WireGuard tunnel. It is the port number that the virtual client will + // listen on. + // Since UDP is connection-less, the port is assigned to the source SocketAddr for up to `UDP_TIMEOUT_SECONDS`; + // every datagram resets the timer for that SocketAddr. Each IP address also has a limit of active connections, + // discarding the LRU ports. + // TODO: UDP Port Pool + } +} diff --git a/src/virtual_device.rs b/src/virtual_device.rs index b8a61ce..992e7d0 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -1,3 +1,4 @@ +use crate::virtual_iface::VirtualPort; use crate::wg::WireGuardTunnel; use anyhow::Context; use smoltcp::phy::{Device, DeviceCapabilities, Medium}; @@ -15,7 +16,7 @@ pub struct VirtualIpDevice { } impl VirtualIpDevice { - pub fn new(virtual_port: u16, wg: Arc) -> anyhow::Result { + pub fn new(virtual_port: VirtualPort, wg: Arc) -> anyhow::Result { let ip_dispatch_rx = wg .register_virtual_interface(virtual_port) .with_context(|| "Failed to register IP dispatch for virtual interface")?; diff --git a/src/virtual_iface/mod.rs b/src/virtual_iface/mod.rs index b9d3354..d2ceb53 100644 --- a/src/virtual_iface/mod.rs +++ b/src/virtual_iface/mod.rs @@ -1,6 +1,8 @@ pub mod tcp; +use crate::config::PortProtocol; use async_trait::async_trait; +use std::fmt::{Display, Formatter}; #[async_trait] pub trait VirtualInterfacePoll { @@ -8,3 +10,13 @@ pub trait VirtualInterfacePoll { /// to the WireGuard tunnel and to the real client. async fn poll_loop(mut self) -> anyhow::Result<()>; } + +/// Virtual port. +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct VirtualPort(pub u16, pub PortProtocol); + +impl Display for VirtualPort { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[{}:{}]", self.0, self.1) + } +} diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index c9fee95..fc8348c 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -1,6 +1,6 @@ -use crate::config::PortForwardConfig; +use crate::config::{PortForwardConfig, PortProtocol}; use crate::virtual_device::VirtualIpDevice; -use crate::virtual_iface::VirtualInterfacePoll; +use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::wg::WireGuardTunnel; use anyhow::Context; use async_trait::async_trait; @@ -74,8 +74,9 @@ impl VirtualInterfacePoll for TcpVirtualInterface { // Consumer for IP packets to send through the virtual interface // Initialize the interface - let device = VirtualIpDevice::new(self.virtual_port, self.wg) - .with_context(|| "Failed to initialize TCP VirtualIpDevice")?; + let device = + VirtualIpDevice::new(VirtualPort(self.virtual_port, PortProtocol::Tcp), self.wg) + .with_context(|| "Failed to initialize TCP VirtualIpDevice")?; let mut virtual_interface = InterfaceBuilder::new(device) .ip_addrs([ // Interface handles IP packets for the sender and recipient diff --git a/src/wg.rs b/src/wg.rs index 498fb98..d10ad50 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -8,7 +8,8 @@ use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet, TcpPacket}; use tokio::net::UdpSocket; use tokio::sync::RwLock; -use crate::config::Config; +use crate::config::{Config, PortProtocol}; +use crate::virtual_iface::VirtualPort; /// The capacity of the channel for received IP packets. const DISPATCH_CAPACITY: usize = 1_000; @@ -26,7 +27,7 @@ pub struct WireGuardTunnel { /// The address of the public WireGuard endpoint (UDP). pub(crate) endpoint: SocketAddr, /// Maps virtual ports to the corresponding IP packet dispatcher. - virtual_port_ip_tx: lockfree::map::Map>>, + virtual_port_ip_tx: lockfree::map::Map>>, /// IP packet dispatcher for unroutable packets. `None` if not initialized. sink_ip_tx: RwLock>>>, } @@ -86,7 +87,7 @@ impl WireGuardTunnel { /// Register a virtual interface (using its assigned virtual port) with the given IP packet `Sender`. pub fn register_virtual_interface( &self, - virtual_port: u16, + virtual_port: VirtualPort, ) -> anyhow::Result>> { let existing = self.virtual_port_ip_tx.get(&virtual_port); if existing.is_some() { @@ -111,7 +112,7 @@ impl WireGuardTunnel { } /// Releases the virtual interface from IP dispatch. - pub fn release_virtual_interface(&self, virtual_port: u16) { + pub fn release_virtual_interface(&self, virtual_port: VirtualPort) { self.virtual_port_ip_tx.remove(&virtual_port); } @@ -296,8 +297,12 @@ impl WireGuardTunnel { TcpPacket::new_checked(segment) .ok() .map(|tcp| { - if self.virtual_port_ip_tx.get(&tcp.dst_port()).is_some() { - RouteResult::Dispatch(tcp.dst_port()) + if self + .virtual_port_ip_tx + .get(&VirtualPort(tcp.dst_port(), PortProtocol::Tcp)) + .is_some() + { + RouteResult::Dispatch(VirtualPort(tcp.dst_port(), PortProtocol::Tcp)) } else if tcp.rst() { RouteResult::Drop } else { @@ -347,7 +352,7 @@ fn trace_ip_packet(message: &str, packet: &[u8]) { enum RouteResult { /// Dispatch the packet to the virtual port. - Dispatch(u16), + Dispatch(VirtualPort), /// The packet is not routable, and should be sent to the sink interface. Sink, /// The packet is not routable, and can be safely ignored. From 11c5ec99fd5aef913f210558b03490a5fb6b88da Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Wed, 20 Oct 2021 16:05:04 -0400 Subject: [PATCH 023/165] Replace lockfree with tokio::sync --- Cargo.lock | 27 +++++++++------------ Cargo.toml | 2 +- src/main.rs | 2 +- src/tunnel/mod.rs | 2 +- src/tunnel/tcp.rs | 60 ++++++++++++++++++++++++----------------------- src/tunnel/udp.rs | 2 ++ src/wg.rs | 10 ++++---- 7 files changed, 52 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3ba00d..885b05b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,16 @@ dependencies = [ "libc", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -469,15 +479,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "lockfree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ee94b5ad113c7cb98c5a040f783d0952ee4fe100993881d1673c2cb002dd23" -dependencies = [ - "owned-alloc", -] - [[package]] name = "log" version = "0.4.14" @@ -609,8 +610,8 @@ dependencies = [ "async-trait", "boringtun", "clap", + "dashmap", "futures", - "lockfree", "log", "nom", "pretty_env_logger", @@ -619,12 +620,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "owned-alloc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30fceb411f9a12ff9222c5f824026be368ff15dc2f13468d850c7d3f502205d6" - [[package]] name = "parking_lot" version = "0.11.2" diff --git a/Cargo.toml b/Cargo.toml index d552c2b..905ac32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,8 @@ pretty_env_logger = "0.3" anyhow = "1" smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", branch = "master" } tokio = { version = "1", features = ["full"] } -lockfree = "0.5.1" futures = "0.3.17" rand = "0.8.4" nom = "7" async-trait = "0.1.51" +dashmap = "4.0.2" diff --git a/src/main.rs b/src/main.rs index 0d301d0..b2f1f75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ async fn main() -> anyhow::Result<()> { init_logger(&config)?; // Initialize the port pool for each protocol - let tcp_port_pool = Arc::new(TcpPortPool::new()); + let tcp_port_pool = TcpPortPool::new(); // TODO: udp_port_pool let wg = WireGuardTunnel::new(&config) diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index 00fe4e8..a4042c9 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -12,7 +12,7 @@ pub mod udp; pub async fn port_forward( port_forward: PortForwardConfig, source_peer_ip: IpAddr, - tcp_port_pool: Arc, + tcp_port_pool: TcpPortPool, wg: Arc, ) -> anyhow::Result<()> { info!( diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index 1f01642..fbbdbc2 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -3,6 +3,7 @@ use crate::virtual_iface::tcp::TcpVirtualInterface; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::wg::WireGuardTunnel; use anyhow::Context; +use std::collections::{HashSet, VecDeque}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::net::{TcpListener, TcpStream}; @@ -20,7 +21,7 @@ const PORT_RANGE: Range = MIN_PORT..MAX_PORT; /// Starts the server that listens on TCP connections. pub async fn tcp_proxy_server( port_forward: PortForwardConfig, - port_pool: Arc, + port_pool: TcpPortPool, wg: Arc, ) -> anyhow::Result<()> { let listener = TcpListener::bind(port_forward.source) @@ -38,7 +39,7 @@ pub async fn tcp_proxy_server( // Assign a 'virtual port': this is a unique port number used to route IP packets // received from the WireGuard tunnel. It is the port number that the virtual client will // listen on. - let virtual_port = match port_pool.next() { + let virtual_port = match port_pool.next().await { Ok(port) => port, Err(e) => { error!( @@ -52,7 +53,7 @@ pub async fn tcp_proxy_server( info!("[{}] Incoming connection from {}", virtual_port, peer_addr); tokio::spawn(async move { - let port_pool = Arc::clone(&port_pool); + let port_pool = port_pool.clone(); let result = handle_tcp_proxy_connection(socket, virtual_port, port_forward, wg.clone()).await; @@ -67,7 +68,7 @@ pub async fn tcp_proxy_server( // Release port when connection drops wg.release_virtual_interface(VirtualPort(virtual_port, PortProtocol::Tcp)); - port_pool.release(virtual_port); + port_pool.release(virtual_port).await; }); } } @@ -203,12 +204,9 @@ async fn handle_tcp_proxy_connection( } /// A pool of virtual ports available for TCP connections. -/// This structure is thread-safe and lock-free; you can use it safely in an `Arc`. +#[derive(Clone)] pub struct TcpPortPool { - /// Remaining ports - inner: lockfree::queue::Queue, - /// Ports in use, with their associated IP channel sender. - taken: lockfree::set::Set, + inner: Arc>, } impl Default for TcpPortPool { @@ -220,37 +218,41 @@ impl Default for TcpPortPool { impl TcpPortPool { /// Initializes a new pool of virtual ports. pub fn new() -> Self { - let inner = lockfree::queue::Queue::default(); + let mut inner = TcpPortPoolInner::default(); let mut ports: Vec = PORT_RANGE.collect(); ports.shuffle(&mut thread_rng()); - ports.into_iter().for_each(|p| inner.push(p) as ()); + ports + .into_iter() + .for_each(|p| inner.queue.push_back(p) as ()); Self { - inner, - taken: lockfree::set::Set::new(), + inner: Arc::new(tokio::sync::RwLock::new(inner)), } } /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). - pub fn next(&self) -> anyhow::Result { - let port = self - .inner - .pop() + pub async fn next(&self) -> anyhow::Result { + let mut inner = self.inner.write().await; + let port = inner + .queue + .pop_front() .with_context(|| "Virtual port pool is exhausted")?; - self.taken - .insert(port) - .ok() - .with_context(|| "Failed to insert taken")?; + inner.taken.insert(port); Ok(port) } /// Releases a port back into the pool. - pub fn release(&self, port: u16) { - self.inner.push(port); - self.taken.remove(&port); - } - - /// Whether the given port is in use by a virtual interface. - pub fn is_in_use(&self, port: u16) -> bool { - self.taken.contains(&port) + pub async fn release(&self, port: u16) { + let mut inner = self.inner.write().await; + inner.queue.push_back(port); + inner.taken.remove(&port); } } + +/// Non thread-safe inner logic for TCP port pool. +#[derive(Debug, Clone, Default)] +struct TcpPortPoolInner { + /// Remaining ports in the pool. + queue: VecDeque, + /// Ports taken out of the pool. + taken: HashSet, +} diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index 7f9fda4..2326351 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -9,9 +9,11 @@ use crate::wg::WireGuardTunnel; const MAX_PACKET: usize = 65536; /// How long to keep the UDP peer address assigned to its virtual specified port, in seconds. +/// TODO: Make this configurable by the CLI const UDP_TIMEOUT_SECONDS: u64 = 60; /// To prevent port-flooding, we set a limit on the amount of open ports per IP address. +/// TODO: Make this configurable by the CLI const PORTS_PER_IP: usize = 100; pub async fn udp_proxy_server( diff --git a/src/wg.rs b/src/wg.rs index d10ad50..653568e 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -27,7 +27,7 @@ pub struct WireGuardTunnel { /// The address of the public WireGuard endpoint (UDP). pub(crate) endpoint: SocketAddr, /// Maps virtual ports to the corresponding IP packet dispatcher. - virtual_port_ip_tx: lockfree::map::Map>>, + virtual_port_ip_tx: dashmap::DashMap>>, /// IP packet dispatcher for unroutable packets. `None` if not initialized. sink_ip_tx: RwLock>>>, } @@ -41,7 +41,7 @@ impl WireGuardTunnel { .await .with_context(|| "Failed to create UDP socket for WireGuard connection")?; let endpoint = config.endpoint_addr; - let virtual_port_ip_tx = lockfree::map::Map::new(); + let virtual_port_ip_tx = Default::default(); Ok(Self { source_peer_ip, @@ -89,8 +89,8 @@ impl WireGuardTunnel { &self, virtual_port: VirtualPort, ) -> anyhow::Result>> { - let existing = self.virtual_port_ip_tx.get(&virtual_port); - if existing.is_some() { + let existing = self.virtual_port_ip_tx.contains_key(&virtual_port); + if existing { Err(anyhow::anyhow!("Cannot register virtual interface with virtual port {} because it is already registered", virtual_port)) } else { let (sender, receiver) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); @@ -215,7 +215,7 @@ impl WireGuardTunnel { RouteResult::Dispatch(port) => { let sender = self.virtual_port_ip_tx.get(&port); if let Some(sender_guard) = sender { - let sender = sender_guard.val(); + let sender = sender_guard.value(); match sender.send(packet.to_vec()).await { Ok(_) => { trace!( From cc91cce169ff1fcbe883dc3dfaccfa356654b1a7 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Wed, 20 Oct 2021 16:49:24 -0400 Subject: [PATCH 024/165] Basic UDP port pool --- src/main.rs | 9 ++--- src/tunnel/mod.rs | 4 ++- src/tunnel/tcp.rs | 10 ++---- src/tunnel/udp.rs | 88 +++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 93 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index b2f1f75..d20138d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use anyhow::Context; use crate::config::Config; use crate::tunnel::tcp::TcpPortPool; +use crate::tunnel::udp::UdpPortPool; use crate::wg::WireGuardTunnel; pub mod config; @@ -23,7 +24,7 @@ async fn main() -> anyhow::Result<()> { // Initialize the port pool for each protocol let tcp_port_pool = TcpPortPool::new(); - // TODO: udp_port_pool + let udp_port_pool = UdpPortPool::new(); let wg = WireGuardTunnel::new(&config) .await @@ -54,12 +55,12 @@ async fn main() -> anyhow::Result<()> { port_forwards .into_iter() - .map(|pf| (pf, wg.clone(), tcp_port_pool.clone())) - .for_each(move |(pf, wg, tcp_port_pool)| { + .map(|pf| (pf, wg.clone(), tcp_port_pool.clone(), udp_port_pool.clone())) + .for_each(move |(pf, wg, tcp_port_pool, udp_port_pool)| { std::thread::spawn(move || { let cpu_pool = tokio::runtime::Runtime::new().unwrap(); cpu_pool.block_on(async move { - tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, wg) + tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg) .await .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) }); diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index a4042c9..c7f2a67 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use crate::config::{PortForwardConfig, PortProtocol}; use crate::tunnel::tcp::TcpPortPool; +use crate::tunnel::udp::UdpPortPool; use crate::wg::WireGuardTunnel; pub mod tcp; @@ -13,6 +14,7 @@ pub async fn port_forward( port_forward: PortForwardConfig, source_peer_ip: IpAddr, tcp_port_pool: TcpPortPool, + udp_port_pool: UdpPortPool, wg: Arc, ) -> anyhow::Result<()> { info!( @@ -26,6 +28,6 @@ pub async fn port_forward( match port_forward.protocol { PortProtocol::Tcp => tcp::tcp_proxy_server(port_forward, tcp_port_pool, wg).await, - PortProtocol::Udp => udp::udp_proxy_server(port_forward, /* udp_port_pool, */ wg).await, + PortProtocol::Udp => udp::udp_proxy_server(port_forward, udp_port_pool, wg).await, } } diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index fbbdbc2..f49aa7c 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -3,7 +3,7 @@ use crate::virtual_iface::tcp::TcpVirtualInterface; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::wg::WireGuardTunnel; use anyhow::Context; -use std::collections::{HashSet, VecDeque}; +use std::collections::VecDeque; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::net::{TcpListener, TcpStream}; @@ -235,8 +235,7 @@ impl TcpPortPool { let port = inner .queue .pop_front() - .with_context(|| "Virtual port pool is exhausted")?; - inner.taken.insert(port); + .with_context(|| "TCP virtual port pool is exhausted")?; Ok(port) } @@ -244,15 +243,12 @@ impl TcpPortPool { pub async fn release(&self, port: u16) { let mut inner = self.inner.write().await; inner.queue.push_back(port); - inner.taken.remove(&port); } } /// Non thread-safe inner logic for TCP port pool. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] struct TcpPortPoolInner { /// Remaining ports in the pool. queue: VecDeque, - /// Ports taken out of the pool. - taken: HashSet, } diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index 2326351..a92bd5e 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -1,12 +1,22 @@ +use std::collections::{HashMap, VecDeque}; +use std::net::{IpAddr, SocketAddr}; +use std::ops::Range; use std::sync::Arc; +use std::time::Instant; use anyhow::Context; +use rand::seq::SliceRandom; +use rand::thread_rng; use tokio::net::UdpSocket; -use crate::config::PortForwardConfig; +use crate::config::{PortForwardConfig, PortProtocol}; +use crate::virtual_iface::VirtualPort; use crate::wg::WireGuardTunnel; const MAX_PACKET: usize = 65536; +const MIN_PORT: u16 = 1000; +const MAX_PORT: u16 = 60999; +const PORT_RANGE: Range = MIN_PORT..MAX_PORT; /// How long to keep the UDP peer address assigned to its virtual specified port, in seconds. /// TODO: Make this configurable by the CLI @@ -18,6 +28,7 @@ const PORTS_PER_IP: usize = 100; pub async fn udp_proxy_server( port_forward: PortForwardConfig, + port_pool: UdpPortPool, wg: Arc, ) -> anyhow::Result<()> { let socket = UdpSocket::bind(port_forward.source) @@ -33,14 +44,79 @@ pub async fn udp_proxy_server( let _wg = wg.clone(); let _data = &buffer[..size].to_vec(); - debug!("Received datagram of {} bytes from {}", size, peer_addr); // Assign a 'virtual port': this is a unique port number used to route IP packets // received from the WireGuard tunnel. It is the port number that the virtual client will // listen on. - // Since UDP is connection-less, the port is assigned to the source SocketAddr for up to `UDP_TIMEOUT_SECONDS`; - // every datagram resets the timer for that SocketAddr. Each IP address also has a limit of active connections, - // discarding the LRU ports. - // TODO: UDP Port Pool + let port = match port_pool.next(peer_addr).await { + Ok(port) => port, + Err(e) => { + error!( + "Failed to assign virtual port number for UDP datagram from [{}]: {:?}", + peer_addr, e + ); + continue; + } + }; + + let port = VirtualPort(port, PortProtocol::Udp); + debug!( + "[{}] Received datagram of {} bytes from {}", + port, size, peer_addr + ); } } + +/// A pool of virtual ports available for TCP connections. +#[derive(Clone)] +pub struct UdpPortPool { + inner: Arc>, +} + +impl Default for UdpPortPool { + fn default() -> Self { + Self::new() + } +} + +impl UdpPortPool { + /// Initializes a new pool of virtual ports. + pub fn new() -> Self { + let mut inner = UdpPortPoolInner::default(); + let mut ports: Vec = PORT_RANGE.collect(); + ports.shuffle(&mut thread_rng()); + ports + .into_iter() + .for_each(|p| inner.queue.push_back(p) as ()); + Self { + inner: Arc::new(tokio::sync::RwLock::new(inner)), + } + } + + /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). + pub async fn next(&self, peer_addr: SocketAddr) -> anyhow::Result { + { + let inner = self.inner.read().await; + if let Some(port) = inner.port_by_peer_addr.get(&peer_addr) { + return Ok(*port); + } + } + + let mut inner = self.inner.write().await; + let port = inner + .queue + .pop_front() + .with_context(|| "UDP virtual port pool is exhausted")?; + inner.port_by_peer_addr.insert(peer_addr, port); + Ok(port) + } +} + +/// Non thread-safe inner logic for UDP port pool. +#[derive(Debug, Default)] +struct UdpPortPoolInner { + /// Remaining ports in the pool. + queue: VecDeque, + /// The port assigned by peer IP/port. + port_by_peer_addr: HashMap, +} From fb50ee7113c138ac536dbda87492c028c82d79d0 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Wed, 20 Oct 2021 18:06:35 -0400 Subject: [PATCH 025/165] UDP virtual interface skeleton --- src/tunnel/udp.rs | 144 +++++++++++++++++++++++++++++++-------- src/virtual_device.rs | 8 ++- src/virtual_iface/mod.rs | 1 + src/virtual_iface/tcp.rs | 9 ++- src/virtual_iface/udp.rs | 68 ++++++++++++++++++ src/wg.rs | 8 +-- 6 files changed, 203 insertions(+), 35 deletions(-) create mode 100644 src/virtual_iface/udp.rs diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index a92bd5e..db232cf 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, VecDeque}; use std::net::{IpAddr, SocketAddr}; use std::ops::Range; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Instant; @@ -10,7 +11,8 @@ use rand::thread_rng; use tokio::net::UdpSocket; use crate::config::{PortForwardConfig, PortProtocol}; -use crate::virtual_iface::VirtualPort; +use crate::virtual_iface::udp::UdpVirtualInterface; +use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::wg::WireGuardTunnel; const MAX_PACKET: usize = 65536; @@ -31,40 +33,120 @@ pub async fn udp_proxy_server( port_pool: UdpPortPool, wg: Arc, ) -> anyhow::Result<()> { + // Abort signal + let abort = Arc::new(AtomicBool::new(false)); + + // data_to_real_client_(tx/rx): This task reads the data from this mpsc channel to send back + // to the real client. + let (data_to_real_client_tx, mut data_to_real_client_rx) = + tokio::sync::mpsc::channel::<(VirtualPort, Vec)>(1_000); + + // data_to_real_server_(tx/rx): This task sends the data received from the real client to the + // virtual interface (virtual server socket). + let (data_to_virtual_server_tx, data_to_virtual_server_rx) = + tokio::sync::mpsc::channel::<(VirtualPort, Vec)>(1_000); + + { + // Spawn virtual interface + // Note: contrary to TCP, there is only one UDP virtual interface + let virtual_interface = UdpVirtualInterface::new( + port_forward, + wg, + data_to_real_client_tx, + data_to_virtual_server_rx, + ); + let abort = abort.clone(); + tokio::spawn(async move { + virtual_interface.poll_loop().await.unwrap_or_else(|e| { + error!("Virtual interface poll loop failed unexpectedly: {}", e); + abort.store(true, Ordering::Relaxed); + }); + }); + } + let socket = UdpSocket::bind(port_forward.source) .await .with_context(|| "Failed to bind on UDP proxy address")?; let mut buffer = [0u8; MAX_PACKET]; loop { - let (size, peer_addr) = socket - .recv_from(&mut buffer) - .await - .with_context(|| "Failed to accept incoming UDP datagram")?; - - let _wg = wg.clone(); - let _data = &buffer[..size].to_vec(); - - // Assign a 'virtual port': this is a unique port number used to route IP packets - // received from the WireGuard tunnel. It is the port number that the virtual client will - // listen on. - let port = match port_pool.next(peer_addr).await { - Ok(port) => port, - Err(e) => { - error!( - "Failed to assign virtual port number for UDP datagram from [{}]: {:?}", - peer_addr, e - ); - continue; + if abort.load(Ordering::Relaxed) { + break; + } + tokio::select! { + to_send_result = next_udp_datagram(&socket, &mut buffer, port_pool.clone()) => { + match to_send_result { + Ok(Some((port, data))) => { + data_to_virtual_server_tx.send((port, data)).await.unwrap_or_else(|e| { + error!( + "Failed to dispatch data to UDP virtual interface: {:?}", + e + ); + }); + } + Ok(None) => { + continue; + } + Err(e) => { + error!( + "Failed to read from client UDP socket: {:?}", + e + ); + break; + } + } } - }; - - let port = VirtualPort(port, PortProtocol::Udp); - debug!( - "[{}] Received datagram of {} bytes from {}", - port, size, peer_addr - ); + data_recv_result = data_to_real_client_rx.recv() => { + if let Some((port, data)) = data_recv_result { + if let Some(peer_addr) = port_pool.get_peer_addr(port.0).await { + if let Err(e) = socket.send_to(&data, peer_addr).await { + error!( + "[{}] Failed to send UDP datagram to real client ({}): {:?}", + port, + peer_addr, + e, + ); + } + } + } + } + } } + Ok(()) +} + +async fn next_udp_datagram( + socket: &UdpSocket, + buffer: &mut [u8], + port_pool: UdpPortPool, +) -> anyhow::Result)>> { + let (size, peer_addr) = socket + .recv_from(buffer) + .await + .with_context(|| "Failed to accept incoming UDP datagram")?; + + // Assign a 'virtual port': this is a unique port number used to route IP packets + // received from the WireGuard tunnel. It is the port number that the virtual client will + // listen on. + let port = match port_pool.next(peer_addr).await { + Ok(port) => port, + Err(e) => { + error!( + "Failed to assign virtual port number for UDP datagram from [{}]: {:?}", + peer_addr, e + ); + return Ok(None); + } + }; + let port = VirtualPort(port, PortProtocol::Udp); + + debug!( + "[{}] Received datagram of {} bytes from {}", + port, size, peer_addr + ); + + let data = buffer[..size].to_vec(); + Ok(Some((port, data))) } /// A pool of virtual ports available for TCP connections. @@ -108,8 +190,14 @@ impl UdpPortPool { .pop_front() .with_context(|| "UDP virtual port pool is exhausted")?; inner.port_by_peer_addr.insert(peer_addr, port); + inner.peer_addr_by_port.insert(port, peer_addr); Ok(port) } + + pub async fn get_peer_addr(&self, port: u16) -> Option { + let inner = self.inner.read().await; + inner.peer_addr_by_port.get(&port).copied() + } } /// Non thread-safe inner logic for UDP port pool. @@ -119,4 +207,6 @@ struct UdpPortPoolInner { queue: VecDeque, /// The port assigned by peer IP/port. port_by_peer_addr: HashMap, + /// The socket address assigned to a peer IP/port. + peer_addr_by_port: HashMap, } diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 992e7d0..8cd0cdf 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -1,5 +1,5 @@ use crate::virtual_iface::VirtualPort; -use crate::wg::WireGuardTunnel; +use crate::wg::{WireGuardTunnel, DISPATCH_CAPACITY}; use anyhow::Context; use smoltcp::phy::{Device, DeviceCapabilities, Medium}; use smoltcp::time::Instant; @@ -16,9 +16,11 @@ pub struct VirtualIpDevice { } impl VirtualIpDevice { + /// Registers a virtual IP device for a single virtual client. pub fn new(virtual_port: VirtualPort, wg: Arc) -> anyhow::Result { - let ip_dispatch_rx = wg - .register_virtual_interface(virtual_port) + let (ip_dispatch_tx, ip_dispatch_rx) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); + + wg.register_virtual_interface(virtual_port, ip_dispatch_tx) .with_context(|| "Failed to register IP dispatch for virtual interface")?; Ok(Self { wg, ip_dispatch_rx }) diff --git a/src/virtual_iface/mod.rs b/src/virtual_iface/mod.rs index d2ceb53..f3796bd 100644 --- a/src/virtual_iface/mod.rs +++ b/src/virtual_iface/mod.rs @@ -1,4 +1,5 @@ pub mod tcp; +pub mod udp; use crate::config::PortProtocol; use async_trait::async_trait; diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index fc8348c..baa92a7 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -266,7 +266,14 @@ impl VirtualInterfacePoll for TcpVirtualInterface { break; } - tokio::time::sleep(Duration::from_millis(1)).await; + match virtual_interface.poll_delay(&socket_set, loop_start) { + Some(smoltcp::time::Duration::ZERO) => { + continue; + } + _ => { + tokio::time::sleep(Duration::from_millis(1)).await; + } + } } trace!("[{}] Virtual interface task terminated", self.virtual_port); self.abort.store(true, Ordering::Relaxed); diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs new file mode 100644 index 0000000..88f13b6 --- /dev/null +++ b/src/virtual_iface/udp.rs @@ -0,0 +1,68 @@ +use std::sync::Arc; +use std::time::Duration; + +use async_trait::async_trait; + +use crate::config::PortForwardConfig; +use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; +use crate::wg::WireGuardTunnel; + +pub struct UdpVirtualInterface { + port_forward: PortForwardConfig, + wg: Arc, + data_to_real_client_tx: tokio::sync::mpsc::Sender<(VirtualPort, Vec)>, + data_to_virtual_server_rx: tokio::sync::mpsc::Receiver<(VirtualPort, Vec)>, +} + +impl UdpVirtualInterface { + pub fn new( + port_forward: PortForwardConfig, + wg: Arc, + data_to_real_client_tx: tokio::sync::mpsc::Sender<(VirtualPort, Vec)>, + data_to_virtual_server_rx: tokio::sync::mpsc::Receiver<(VirtualPort, Vec)>, + ) -> Self { + Self { + port_forward, + wg, + data_to_real_client_tx, + data_to_virtual_server_rx, + } + } +} + +#[async_trait] +impl VirtualInterfacePoll for UdpVirtualInterface { + async fn poll_loop(self) -> anyhow::Result<()> { + // Data receiver to dispatch using virtual client sockets + let mut data_to_virtual_server_rx = self.data_to_virtual_server_rx; + + // The IP to bind client sockets to + let _source_peer_ip = self.wg.source_peer_ip; + + // The IP/port to bind the server socket to + let _destination = self.port_forward.destination; + + loop { + let _loop_start = smoltcp::time::Instant::now(); + // TODO: smoltcp UDP + + if let Ok((client_port, data)) = data_to_virtual_server_rx.try_recv() { + // TODO: Find the matching client socket and send + // Echo for now + self.data_to_real_client_tx + .send((client_port, data)) + .await + .unwrap_or_else(|e| { + error!( + "[{}] Failed to dispatch data from virtual client to real client: {:?}", + client_port, e + ); + }); + } + + tokio::time::sleep(Duration::from_millis(1)).await; + } + + // Ok(()) + } +} diff --git a/src/wg.rs b/src/wg.rs index 653568e..673da1c 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -12,7 +12,7 @@ use crate::config::{Config, PortProtocol}; use crate::virtual_iface::VirtualPort; /// The capacity of the channel for received IP packets. -const DISPATCH_CAPACITY: usize = 1_000; +pub const DISPATCH_CAPACITY: usize = 1_000; const MAX_PACKET: usize = 65536; /// A WireGuard tunnel. Encapsulates and decapsulates IP packets @@ -88,14 +88,14 @@ impl WireGuardTunnel { pub fn register_virtual_interface( &self, virtual_port: VirtualPort, - ) -> anyhow::Result>> { + sender: tokio::sync::mpsc::Sender>, + ) -> anyhow::Result<()> { let existing = self.virtual_port_ip_tx.contains_key(&virtual_port); if existing { Err(anyhow::anyhow!("Cannot register virtual interface with virtual port {} because it is already registered", virtual_port)) } else { - let (sender, receiver) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); self.virtual_port_ip_tx.insert(virtual_port, sender); - Ok(receiver) + Ok(()) } } From 282d4f48eb0341d24fb2f5090d8771a0cfec3014 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Wed, 20 Oct 2021 19:04:56 -0400 Subject: [PATCH 026/165] Checkpoint --- src/virtual_device.rs | 15 ++++++++-- src/virtual_iface/tcp.rs | 2 +- src/virtual_iface/udp.rs | 65 ++++++++++++++++++++++++++++++++++++++-- src/wg.rs | 6 +++- 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 8cd0cdf..02d60f9 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -5,6 +5,9 @@ use smoltcp::phy::{Device, DeviceCapabilities, Medium}; use smoltcp::time::Instant; use std::sync::Arc; +/// The max transmission unit for WireGuard. +const WG_MTU: usize = 1420; + /// A virtual device that processes IP packets. IP packets received from the WireGuard endpoint /// are made available to this device using a channel receiver. IP packets sent from this device /// are asynchronously sent out to the WireGuard tunnel. @@ -16,8 +19,16 @@ pub struct VirtualIpDevice { } impl VirtualIpDevice { + /// Initializes a new virtual IP device. + pub fn new( + wg: Arc, + ip_dispatch_rx: tokio::sync::mpsc::Receiver>, + ) -> Self { + Self { wg, ip_dispatch_rx } + } + /// Registers a virtual IP device for a single virtual client. - pub fn new(virtual_port: VirtualPort, wg: Arc) -> anyhow::Result { + pub fn new_direct(virtual_port: VirtualPort, wg: Arc) -> anyhow::Result { let (ip_dispatch_tx, ip_dispatch_rx) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); wg.register_virtual_interface(virtual_port, ip_dispatch_tx) @@ -60,7 +71,7 @@ impl<'a> Device<'a> for VirtualIpDevice { fn capabilities(&self) -> DeviceCapabilities { let mut cap = DeviceCapabilities::default(); cap.medium = Medium::Ip; - cap.max_transmission_unit = 1420; + cap.max_transmission_unit = WG_MTU; cap } } diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index baa92a7..a632792 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -75,7 +75,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { // Consumer for IP packets to send through the virtual interface // Initialize the interface let device = - VirtualIpDevice::new(VirtualPort(self.virtual_port, PortProtocol::Tcp), self.wg) + VirtualIpDevice::new_direct(VirtualPort(self.virtual_port, PortProtocol::Tcp), self.wg) .with_context(|| "Failed to initialize TCP VirtualIpDevice")?; let mut virtual_interface = InterfaceBuilder::new(device) .ip_addrs([ diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 88f13b6..139610d 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -1,11 +1,18 @@ +use anyhow::Context; +use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; +use dashmap::DashMap; +use smoltcp::iface::InterfaceBuilder; +use smoltcp::socket::{SocketSet, UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; +use smoltcp::wire::{IpAddress, IpCidr}; use crate::config::PortForwardConfig; +use crate::virtual_device::VirtualIpDevice; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; -use crate::wg::WireGuardTunnel; +use crate::wg::{WireGuardTunnel, DISPATCH_CAPACITY}; pub struct UdpVirtualInterface { port_forward: PortForwardConfig, @@ -37,16 +44,68 @@ impl VirtualInterfacePoll for UdpVirtualInterface { let mut data_to_virtual_server_rx = self.data_to_virtual_server_rx; // The IP to bind client sockets to - let _source_peer_ip = self.wg.source_peer_ip; + let source_peer_ip = self.wg.source_peer_ip; // The IP/port to bind the server socket to - let _destination = self.port_forward.destination; + let destination = self.port_forward.destination; + + // Initialize a channel for IP packets. + // The "base transmitted" is cloned so that each virtual port can register a sender in the tunnel. + // The receiver is given to the device so that the Virtual Interface can process incoming IP packets from the tunnel. + let (base_ip_dispatch_tx, ip_dispatch_rx) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); + + let device = VirtualIpDevice::new(self.wg.clone(), ip_dispatch_rx); + let mut virtual_interface = InterfaceBuilder::new(device) + .ip_addrs([ + // Interface handles IP packets for the sender and recipient + IpCidr::new(source_peer_ip.into(), 32), + IpCidr::new(destination.ip().into(), 32), + ]) + .finalize(); + + // Server socket: this is a placeholder for the interface. + let server_socket: anyhow::Result = { + static mut UDP_SERVER_RX_META: [UdpPacketMetadata; 0] = []; + static mut UDP_SERVER_RX_DATA: [u8; 0] = []; + static mut UDP_SERVER_TX_META: [UdpPacketMetadata; 0] = []; + static mut UDP_SERVER_TX_DATA: [u8; 0] = []; + let udp_rx_buffer = + UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe { + &mut UDP_SERVER_RX_DATA[..] + }); + let udp_tx_buffer = + UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe { + &mut UDP_SERVER_TX_DATA[..] + }); + let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); + + socket + .bind((IpAddress::from(destination.ip()), destination.port())) + .with_context(|| "UDP virtual server socket failed to listen")?; + + Ok(socket) + }; + + let mut socket_set = SocketSet::new(vec![]); + let _server_handle = socket_set.add(server_socket?); loop { let _loop_start = smoltcp::time::Instant::now(); + let wg = self.wg.clone(); // TODO: smoltcp UDP if let Ok((client_port, data)) = data_to_virtual_server_rx.try_recv() { + // Register the socket in WireGuard Tunnel if not already + if !wg.is_registered(client_port) { + wg.register_virtual_interface(client_port, base_ip_dispatch_tx.clone()) + .unwrap_or_else(|e| { + error!( + "[{}] Failed to register UDP socket in WireGuard tunnel", + client_port + ); + }); + } + // TODO: Find the matching client socket and send // Echo for now self.data_to_real_client_tx diff --git a/src/wg.rs b/src/wg.rs index 673da1c..80c7272 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -90,7 +90,7 @@ impl WireGuardTunnel { virtual_port: VirtualPort, sender: tokio::sync::mpsc::Sender>, ) -> anyhow::Result<()> { - let existing = self.virtual_port_ip_tx.contains_key(&virtual_port); + let existing = self.is_registered(virtual_port); if existing { Err(anyhow::anyhow!("Cannot register virtual interface with virtual port {} because it is already registered", virtual_port)) } else { @@ -99,6 +99,10 @@ impl WireGuardTunnel { } } + pub fn is_registered(&self, virtual_port: VirtualPort) -> bool { + self.virtual_port_ip_tx.contains_key(&virtual_port) + } + /// Register a virtual interface (using its assigned virtual port) with the given IP packet `Sender`. pub async fn register_sink_interface( &self, From f40e1f8e532938123a1a06e0863a4639ce09bce7 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Wed, 20 Oct 2021 22:44:42 -0400 Subject: [PATCH 027/165] Add logo Co-authored-by: BorysSerbyn --- .github/onetun.png | Bin 0 -> 139280 bytes README.md | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 .github/onetun.png diff --git a/.github/onetun.png b/.github/onetun.png new file mode 100644 index 0000000000000000000000000000000000000000..24bfcc689461b9b7239529dd9ee0c7f50e5964a7 GIT binary patch literal 139280 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

p$9Vj=nyZestce_?X2* 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­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


Vyg<~z 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 sDA+^69t!qQu!n;GM-+U_toz2 + # onetun A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations. From d975efefaf787934bfe7f6f817ce3230f246c589 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 25 Oct 2021 19:05:40 -0400 Subject: [PATCH 028/165] End-to-end UDP implementation Port re-use still needs to be implemented to prevent exhaustion over time, and flooding. --- src/tunnel/udp.rs | 5 +- src/virtual_iface/udp.rs | 102 ++++++++++++++++++++++++++++++--------- src/wg.rs | 35 +++++++++----- 3 files changed, 106 insertions(+), 36 deletions(-) diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index db232cf..6eb7e02 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -175,7 +175,7 @@ impl UdpPortPool { } } - /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). + /// Requests a free port from the pool. An error is returned if none is available (exhausted max capacity). pub async fn next(&self, peer_addr: SocketAddr) -> anyhow::Result { { let inner = self.inner.read().await; @@ -184,6 +184,9 @@ impl UdpPortPool { } } + // TODO: When the port pool is exhausted, it should re-queue the least recently used port. + // TODO: Limit number of ports in use by peer IP + let mut inner = self.inner.write().await; let port = inner .queue diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 139610d..382d7c3 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -4,9 +4,8 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; -use dashmap::DashMap; use smoltcp::iface::InterfaceBuilder; -use smoltcp::socket::{SocketSet, UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; +use smoltcp::socket::{SocketHandle, SocketSet, UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; use smoltcp::wire::{IpAddress, IpCidr}; use crate::config::PortForwardConfig; @@ -14,6 +13,8 @@ use crate::virtual_device::VirtualIpDevice; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::wg::{WireGuardTunnel, DISPATCH_CAPACITY}; +const MAX_PACKET: usize = 65536; + pub struct UdpVirtualInterface { port_forward: PortForwardConfig, wg: Arc, @@ -89,31 +90,88 @@ impl VirtualInterfacePoll for UdpVirtualInterface { let mut socket_set = SocketSet::new(vec![]); let _server_handle = socket_set.add(server_socket?); + // A map of virtual port to client socket. + let mut client_sockets: HashMap = HashMap::new(); + loop { - let _loop_start = smoltcp::time::Instant::now(); + let loop_start = smoltcp::time::Instant::now(); let wg = self.wg.clone(); - // TODO: smoltcp UDP + + match virtual_interface.poll(&mut socket_set, loop_start) { + Ok(processed) if processed => { + trace!("UDP virtual interface polled some packets to be processed"); + } + Err(e) => error!("UDP virtual interface poll error: {:?}", e), + _ => {} + } + + // Loop through each client socket and check if there is any data to send back + // to the real client. + for (virtual_port, client_socket_handle) in client_sockets.iter() { + let mut client_socket = socket_set.get::(*client_socket_handle); + match client_socket.recv() { + Ok((data, _peer)) => { + // Send the data back to the real client using MPSC channel + self.data_to_real_client_tx + .send((*virtual_port, data.to_vec())) + .await + .unwrap_or_else(|e| { + error!( + "[{}] Failed to dispatch data from virtual client to real client: {:?}", + virtual_port, e + ); + }); + } + Err(smoltcp::Error::Exhausted) => {} + Err(e) => { + error!( + "[{}] Failed to read from virtual client socket: {:?}", + virtual_port, e + ); + } + } + } if let Ok((client_port, data)) = data_to_virtual_server_rx.try_recv() { - // Register the socket in WireGuard Tunnel if not already - if !wg.is_registered(client_port) { - wg.register_virtual_interface(client_port, base_ip_dispatch_tx.clone()) - .unwrap_or_else(|e| { - error!( - "[{}] Failed to register UDP socket in WireGuard tunnel", - client_port - ); - }); - } - - // TODO: Find the matching client socket and send - // Echo for now - self.data_to_real_client_tx - .send((client_port, data)) - .await + // Register the socket in WireGuard Tunnel (overrides any previous registration as well) + wg.register_virtual_interface(client_port, base_ip_dispatch_tx.clone()) .unwrap_or_else(|e| { error!( - "[{}] Failed to dispatch data from virtual client to real client: {:?}", + "[{}] Failed to register UDP socket in WireGuard tunnel: {:?}", + client_port, e + ); + }); + + let client_socket_handle = client_sockets.entry(client_port).or_insert_with(|| { + let rx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; + let tx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; + let rx_data = vec![0u8; MAX_PACKET]; + let tx_data = vec![0u8; MAX_PACKET]; + let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data); + let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data); + let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); + + socket + .bind((IpAddress::from(wg.source_peer_ip), client_port.0)) + .unwrap_or_else(|e| { + error!( + "[{}] UDP virtual client socket failed to bind: {:?}", + client_port, e + ); + }); + + socket_set.add(socket) + }); + + let mut client_socket = socket_set.get::(*client_socket_handle); + client_socket + .send_slice( + &data, + (IpAddress::from(destination.ip()), destination.port()).into(), + ) + .unwrap_or_else(|e| { + error!( + "[{}] Failed to send data to virtual server: {:?}", client_port, e ); }); @@ -121,7 +179,5 @@ impl VirtualInterfacePoll for UdpVirtualInterface { tokio::time::sleep(Duration::from_millis(1)).await; } - - // Ok(()) } } diff --git a/src/wg.rs b/src/wg.rs index 80c7272..2dc20d3 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -4,7 +4,7 @@ use std::time::Duration; use anyhow::Context; use boringtun::noise::{Tunn, TunnResult}; use log::Level; -use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet, TcpPacket}; +use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket}; use tokio::net::UdpSocket; use tokio::sync::RwLock; @@ -90,17 +90,8 @@ impl WireGuardTunnel { virtual_port: VirtualPort, sender: tokio::sync::mpsc::Sender>, ) -> anyhow::Result<()> { - let existing = self.is_registered(virtual_port); - if existing { - Err(anyhow::anyhow!("Cannot register virtual interface with virtual port {} because it is already registered", virtual_port)) - } else { - self.virtual_port_ip_tx.insert(virtual_port, sender); - Ok(()) - } - } - - pub fn is_registered(&self, virtual_port: VirtualPort) -> bool { - self.virtual_port_ip_tx.contains_key(&virtual_port) + self.virtual_port_ip_tx.insert(virtual_port, sender); + Ok(()) } /// Register a virtual interface (using its assigned virtual port) with the given IP packet `Sender`. @@ -276,6 +267,7 @@ impl WireGuardTunnel { .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip) .map(|packet| match packet.protocol() { IpProtocol::Tcp => Some(self.route_tcp_segment(packet.payload())), + IpProtocol::Udp => Some(self.route_udp_datagram(packet.payload())), // Unrecognized protocol, so we cannot determine where to route _ => Some(RouteResult::Drop), }) @@ -287,6 +279,7 @@ impl WireGuardTunnel { .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip) .map(|packet| match packet.next_header() { IpProtocol::Tcp => Some(self.route_tcp_segment(packet.payload())), + IpProtocol::Udp => Some(self.route_udp_datagram(packet.payload())), // Unrecognized protocol, so we cannot determine where to route _ => Some(RouteResult::Drop), }) @@ -316,6 +309,24 @@ impl WireGuardTunnel { .unwrap_or(RouteResult::Drop) } + /// Makes a decision on the handling of an incoming UDP datagram. + fn route_udp_datagram(&self, datagram: &[u8]) -> RouteResult { + UdpPacket::new_checked(datagram) + .ok() + .map(|udp| { + if self + .virtual_port_ip_tx + .get(&VirtualPort(udp.dst_port(), PortProtocol::Udp)) + .is_some() + { + RouteResult::Dispatch(VirtualPort(udp.dst_port(), PortProtocol::Udp)) + } else { + RouteResult::Drop + } + }) + .unwrap_or(RouteResult::Drop) + } + /// Route a packet to the IP sink interface. async fn route_ip_sink(&self, packet: &[u8]) -> anyhow::Result<()> { let ip_sink_tx = self.sink_ip_tx.read().await; From faf157cfeb759d20130895e10feb77b01ca0fa13 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 00:03:44 -0400 Subject: [PATCH 029/165] UDP port re-use during flooding --- Cargo.lock | 27 +++++++++++++++ Cargo.toml | 1 + src/tunnel/udp.rs | 86 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 104 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 885b05b..851e8c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,6 +374,12 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -398,6 +404,16 @@ dependencies = [ "quick-error", ] +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.11" @@ -615,6 +631,7 @@ dependencies = [ "log", "nom", "pretty_env_logger", + "priority-queue", "rand", "smoltcp", "tokio", @@ -674,6 +691,16 @@ dependencies = [ "log", ] +[[package]] +name = "priority-queue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf40e51ccefb72d42720609e1d3c518de8b5800d723a09358d4a6d6245e1f8ca" +dependencies = [ + "autocfg", + "indexmap", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" diff --git a/Cargo.toml b/Cargo.toml index 905ac32..9549a80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ rand = "0.8.4" nom = "7" async-trait = "0.1.51" dashmap = "4.0.2" +priority-queue = "1.2.0" diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index 6eb7e02..bedcdf7 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::{BTreeMap, HashMap, VecDeque}; use std::net::{IpAddr, SocketAddr}; use std::ops::Range; use std::sync::atomic::{AtomicBool, Ordering}; @@ -6,6 +6,8 @@ use std::sync::Arc; use std::time::Instant; use anyhow::Context; +use priority_queue::double_priority_queue::DoublePriorityQueue; +use priority_queue::priority_queue::PriorityQueue; use rand::seq::SliceRandom; use rand::thread_rng; use tokio::net::UdpSocket; @@ -107,6 +109,7 @@ pub async fn udp_proxy_server( e, ); } + port_pool.update_last_transmit(port.0).await; } } } @@ -145,6 +148,8 @@ async fn next_udp_datagram( port, size, peer_addr ); + port_pool.update_last_transmit(port.0).await; + let data = buffer[..size].to_vec(); Ok(Some((port, data))) } @@ -177,26 +182,81 @@ impl UdpPortPool { /// Requests a free port from the pool. An error is returned if none is available (exhausted max capacity). pub async fn next(&self, peer_addr: SocketAddr) -> anyhow::Result { + // A port found to be reused. This is outside of the block because the read lock cannot be upgraded to a write lock. + let mut port_reuse: Option = None; + { let inner = self.inner.read().await; if let Some(port) = inner.port_by_peer_addr.get(&peer_addr) { return Ok(*port); } + + // Count how many ports are being used by the peer IP + let peer_ip = peer_addr.ip(); + let peer_port_count = inner + .peer_port_usage + .get(&peer_ip) + .map(|v| v.len()) + .unwrap_or_default(); + + if peer_port_count >= PORTS_PER_IP { + // Return least recently used port in this IP's pool + port_reuse = Some( + *(inner + .peer_port_usage + .get(&peer_ip) + .unwrap() + .peek_min() + .unwrap() + .0), + ); + warn!( + "Peer [{}] is re-using active virtual port {} due to self-exhaustion.", + peer_addr, + port_reuse.unwrap() + ); + } } - // TODO: When the port pool is exhausted, it should re-queue the least recently used port. - // TODO: Limit number of ports in use by peer IP - let mut inner = self.inner.write().await; - let port = inner - .queue - .pop_front() - .with_context(|| "UDP virtual port pool is exhausted")?; + + let port = port_reuse + .or_else(|| inner.queue.pop_front()) + .or_else(|| { + // If there is no port to reuse, and the port pool is exhausted, take the last recently used port overall, + // as long as the last transmission exceeds the deadline + let last: (&u16, &Instant) = inner.port_usage.peek_min().unwrap(); + if Instant::now().duration_since(*last.1).as_secs() > UDP_TIMEOUT_SECONDS { + warn!( + "Peer [{}] is re-using inactive virtual port {} due to global exhaustion.", + peer_addr, last.0 + ); + Some(*last.0) + } else { + None + } + }) + .with_context(|| "virtual port pool is exhausted")?; + inner.port_by_peer_addr.insert(peer_addr, port); inner.peer_addr_by_port.insert(port, peer_addr); Ok(port) } + /// Notify that the given virtual port has received or transmitted a UDP datagram. + pub async fn update_last_transmit(&self, port: u16) { + let mut inner = self.inner.write().await; + if let Some(peer) = inner.peer_addr_by_port.get(&port).copied() { + let mut pq: &mut DoublePriorityQueue = inner + .peer_port_usage + .entry(peer.ip()) + .or_insert_with(Default::default); + pq.push(port, Instant::now()); + } + let mut pq: &mut DoublePriorityQueue = &mut inner.port_usage; + pq.push(port, Instant::now()); + } + pub async fn get_peer_addr(&self, port: u16) -> Option { let inner = self.inner.read().await; inner.peer_addr_by_port.get(&port).copied() @@ -208,8 +268,14 @@ impl UdpPortPool { struct UdpPortPoolInner { /// Remaining ports in the pool. queue: VecDeque, - /// The port assigned by peer IP/port. + /// The port assigned by peer IP/port. This is used to lookup an existing virtual port + /// for an incoming UDP datagram. port_by_peer_addr: HashMap, - /// The socket address assigned to a peer IP/port. + /// The socket address assigned to a peer IP/port. This is used to send a UDP datagram to + /// the real peer address, given the virtual port. peer_addr_by_port: HashMap, + /// Keeps an ordered map of the most recently used virtual ports by a peer (client) IP. + peer_port_usage: HashMap>, + /// Keeps an ordered map of the most recently used virtual ports in general. + port_usage: DoublePriorityQueue, } From 0da6fa51de35ef1d323673e4273ea9eeb545d707 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 00:38:22 -0400 Subject: [PATCH 030/165] udp: use tokio select instead of 1ms loop --- src/virtual_iface/udp.rs | 166 ++++++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 74 deletions(-) diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 382d7c3..5b275de 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -93,91 +93,109 @@ impl VirtualInterfacePoll for UdpVirtualInterface { // A map of virtual port to client socket. let mut client_sockets: HashMap = HashMap::new(); + // The next instant required to poll the virtual interface + // None means "immediate poll required". + let mut next_poll: Option = None; + loop { - let loop_start = smoltcp::time::Instant::now(); let wg = self.wg.clone(); + tokio::select! { + // Wait the recommended amount of time by smoltcp, and poll again. + _ = match next_poll { + None => tokio::time::sleep(Duration::ZERO), + Some(until) => tokio::time::sleep_until(until) + } => { + let loop_start = smoltcp::time::Instant::now(); - match virtual_interface.poll(&mut socket_set, loop_start) { - Ok(processed) if processed => { - trace!("UDP virtual interface polled some packets to be processed"); + match virtual_interface.poll(&mut socket_set, loop_start) { + Ok(processed) if processed => { + trace!("UDP virtual interface polled some packets to be processed"); + } + Err(e) => error!("UDP virtual interface poll error: {:?}", e), + _ => {} + } + + // Loop through each client socket and check if there is any data to send back + // to the real client. + for (virtual_port, client_socket_handle) in client_sockets.iter() { + let mut client_socket = socket_set.get::(*client_socket_handle); + match client_socket.recv() { + Ok((data, _peer)) => { + // Send the data back to the real client using MPSC channel + self.data_to_real_client_tx + .send((*virtual_port, data.to_vec())) + .await + .unwrap_or_else(|e| { + error!( + "[{}] Failed to dispatch data from virtual client to real client: {:?}", + virtual_port, e + ); + }); + } + Err(smoltcp::Error::Exhausted) => {} + Err(e) => { + error!( + "[{}] Failed to read from virtual client socket: {:?}", + virtual_port, e + ); + } + } + } + + next_poll = match virtual_interface.poll_delay(&socket_set, loop_start) { + Some(smoltcp::time::Duration::ZERO) => None, + Some(delay) => Some(tokio::time::Instant::now() + Duration::from_millis(delay.millis())), + None => None, + } } - Err(e) => error!("UDP virtual interface poll error: {:?}", e), - _ => {} - } - - // Loop through each client socket and check if there is any data to send back - // to the real client. - for (virtual_port, client_socket_handle) in client_sockets.iter() { - let mut client_socket = socket_set.get::(*client_socket_handle); - match client_socket.recv() { - Ok((data, _peer)) => { - // Send the data back to the real client using MPSC channel - self.data_to_real_client_tx - .send((*virtual_port, data.to_vec())) - .await + // Wait for data to be received from the real client + data_recv_result = data_to_virtual_server_rx.recv() => { + if let Some((client_port, data)) = data_recv_result { + // Register the socket in WireGuard Tunnel (overrides any previous registration as well) + wg.register_virtual_interface(client_port, base_ip_dispatch_tx.clone()) .unwrap_or_else(|e| { error!( - "[{}] Failed to dispatch data from virtual client to real client: {:?}", - virtual_port, e + "[{}] Failed to register UDP socket in WireGuard tunnel: {:?}", + client_port, e + ); + }); + + let client_socket_handle = client_sockets.entry(client_port).or_insert_with(|| { + let rx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; + let tx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; + let rx_data = vec![0u8; MAX_PACKET]; + let tx_data = vec![0u8; MAX_PACKET]; + let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data); + let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data); + let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); + + socket + .bind((IpAddress::from(wg.source_peer_ip), client_port.0)) + .unwrap_or_else(|e| { + error!( + "[{}] UDP virtual client socket failed to bind: {:?}", + client_port, e + ); + }); + + socket_set.add(socket) + }); + + let mut client_socket = socket_set.get::(*client_socket_handle); + client_socket + .send_slice( + &data, + (IpAddress::from(destination.ip()), destination.port()).into(), + ) + .unwrap_or_else(|e| { + error!( + "[{}] Failed to send data to virtual server: {:?}", + client_port, e ); }); } - Err(smoltcp::Error::Exhausted) => {} - Err(e) => { - error!( - "[{}] Failed to read from virtual client socket: {:?}", - virtual_port, e - ); - } } } - - if let Ok((client_port, data)) = data_to_virtual_server_rx.try_recv() { - // Register the socket in WireGuard Tunnel (overrides any previous registration as well) - wg.register_virtual_interface(client_port, base_ip_dispatch_tx.clone()) - .unwrap_or_else(|e| { - error!( - "[{}] Failed to register UDP socket in WireGuard tunnel: {:?}", - client_port, e - ); - }); - - let client_socket_handle = client_sockets.entry(client_port).or_insert_with(|| { - let rx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; - let tx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; - let rx_data = vec![0u8; MAX_PACKET]; - let tx_data = vec![0u8; MAX_PACKET]; - let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data); - let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data); - let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); - - socket - .bind((IpAddress::from(wg.source_peer_ip), client_port.0)) - .unwrap_or_else(|e| { - error!( - "[{}] UDP virtual client socket failed to bind: {:?}", - client_port, e - ); - }); - - socket_set.add(socket) - }); - - let mut client_socket = socket_set.get::(*client_socket_handle); - client_socket - .send_slice( - &data, - (IpAddress::from(destination.ip()), destination.port()).into(), - ) - .unwrap_or_else(|e| { - error!( - "[{}] Failed to send data to virtual server: {:?}", - client_port, e - ); - }); - } - - tokio::time::sleep(Duration::from_millis(1)).await; } } } From 1493feb1844fd93bbf6419d095fbd3905625b630 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 01:20:02 -0400 Subject: [PATCH 031/165] Reduce udp client socket meta buffer --- src/main.rs | 11 ++++------- src/virtual_iface/udp.rs | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index d20138d..146a65b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,13 +57,10 @@ async fn main() -> anyhow::Result<()> { .into_iter() .map(|pf| (pf, wg.clone(), tcp_port_pool.clone(), udp_port_pool.clone())) .for_each(move |(pf, wg, tcp_port_pool, udp_port_pool)| { - std::thread::spawn(move || { - let cpu_pool = tokio::runtime::Runtime::new().unwrap(); - cpu_pool.block_on(async move { - tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg) - .await - .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) - }); + tokio::spawn(async move { + tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg) + .await + .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) }); }); } diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 5b275de..212cb8d 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -161,8 +161,8 @@ impl VirtualInterfacePoll for UdpVirtualInterface { }); let client_socket_handle = client_sockets.entry(client_port).or_insert_with(|| { - let rx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; - let tx_meta = vec![UdpPacketMetadata::EMPTY; MAX_PACKET]; + let rx_meta = vec![UdpPacketMetadata::EMPTY; 10]; + let tx_meta = vec![UdpPacketMetadata::EMPTY; 10]; let rx_data = vec![0u8; MAX_PACKET]; let tx_data = vec![0u8; MAX_PACKET]; let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data); From 4ecf16bc3fc35927e66222b27654960881193342 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 01:47:48 -0400 Subject: [PATCH 032/165] Update readme --- README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1defff3..eaade12 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ A cross-platform, user-space WireGuard port-forwarder that requires no system ne ## Use-case - You have an existing WireGuard endpoint (router), accessible using its UDP endpoint (typically port 51820); and -- You have a peer on the WireGuard network, running a TCP server on a port accessible to the WireGuard network; and -- You want to access this TCP service from a second computer, on which you can't install WireGuard because you +- You have a peer on the WireGuard network, running a TCP or UDP service on a port accessible to the WireGuard network; and +- You want to access this TCP or UDP service from a second computer, on which you can't install WireGuard because you can't (no root access) or don't want to (polluting OS configs). For example, this can be useful to forward a port from a Kubernetes cluster to a server behind WireGuard, @@ -17,7 +17,7 @@ without needing to install WireGuard in a Pod. ## Usage -**onetun** opens a TCP port on your local system, from which traffic is forwarded to a TCP port on a peer in your +**onetun** opens a TCP or UDP port on your local system, from which traffic is forwarded to a TCP port on a peer in your WireGuard network. It requires no changes to your operating system's network interfaces: you don't need to have `root` access, or install any WireGuard tool on your local system for it to work. @@ -25,12 +25,12 @@ The only prerequisite is to register a peer IP and public key on the remote Wire the WireGuard endpoint to trust the onetun peer and for packets to be routed. ``` -./onetun \ - --endpoint-addr \ - --endpoint-public-key \ - --private-key \ - --source-peer-ip \ - --keep-alive \ +./onetun [src_host:]::[:TCP,UDP,...] [...] \ + --endpoint-addr \ + --endpoint-public-key \ + --private-key \ + --source-peer-ip \ + --keep-alive \ --log Tunneling [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +INFO onetun > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` Which means you can now access the port locally! @@ -84,6 +84,53 @@ $ curl 127.0.0.1:8080 Hello world! ``` +### Multiple tunnels in parallel + +**onetun** supports running multiple tunnels in parallel. For example: + +``` +$ ./onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081 +INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [140.30.3.182:51820] as peer 192.168.4.3) +``` + +... would open TCP ports 8080 and 8081 locally, which forward to their respective ports on the different peers. + +### UDP Support + +**onetun** supports UDP forwarding. You can add `:UDP` at the end of the port-forward configuration, or `UDP,TCP` to support +both protocols on the same port (note that this opens 2 separate tunnels, just on the same port) + +``` +$ ./onetun 127.0.0.1:8080:192.168.4.2:8080:UDP +INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) + +$ ./onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP +INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +``` + +Note: UDP support is totally experimental. You should read the UDP portion of the **Architecture** section before using +it in any production capacity. + +### IPv6 Support + +**onetun** supports both IPv4 and IPv6. In fact, you can use onetun to forward some IP version to another, e.g. 6-to-4: + +``` +$ ./onetun [::1]:8080:192.168.4.2:8080 +INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +``` + +Note that each tunnel can only support one "source" IP version and one "destination" IP version. If you want to support +both IPv4 and IPv6 on the same port, you should create a second port-forward: + +``` +$ ./onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080 +INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +``` + ## Download Normally I would publish `onetun` to crates.io. However, it depends on some features @@ -150,6 +197,22 @@ the virtual client to read it. When the virtual client reads data, it simply pus This work is all made possible by [smoltcp](https://github.com/smoltcp-rs/smoltcp) and [boringtun](https://github.com/cloudflare/boringtun), so special thanks to the developers of those libraries. +### UDP + +UDP support is experimental. Since UDP messages are stateless, there is no perfect way for onetun to know when to release the +assigned virtual port back to the pool for a new peer to use. This would cause issues over time as running out of virtual ports +would mean new datagrams get dropped. To alleviate this, onetun will cap the amount of ports used by one peer IP address; +if another datagram comes in from a different port but with the same IP, the least recently used virtual port will be freed and assigned +to the new peer port. At that point, any datagram packets destined for the reused virtual port will be routed to the new peer, +and any datagrams received by the old peer will be dropped. + +In addition, in cases where many IPs are exhausting the UDP virtual port pool in tandem, and a totally new peer IP sends data, +onetun will have to pick the least recently used virtual port from _any_ peer IP and reuse it. However, this is only allowed +if the least recently used port hasn't been used for a certain amount of time. If all virtual ports are truly "active" +(with at least one transmission within that time limit), the new datagram gets dropped due to exhaustion. + +All in all, I would not recommend using UDP forwarding for public services, since it's most likely prone to simple DoS or DDoS. + ## License MIT. See `LICENSE` for details. From 7545c7a3a82fcfa926e1fc80df08eecd1e6ac7e6 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 01:50:48 -0400 Subject: [PATCH 033/165] release: v0.2.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 851e8c5..b4efdeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,7 +620,7 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.1.11" +version = "0.2.0" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 9549a80..79b6886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.1.11" +version = "0.2.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 75d6a0a11c21c4878d78a2f4cfba2c6266ca1979 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 02:49:14 -0400 Subject: [PATCH 034/165] Allow passing private key using file. Adds warning about passing key directly in CLI. On *nix systems, checks file permissions for warnings. Fixes #19 --- src/config.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 4 +++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0e3a727..835e05f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use std::convert::TryFrom; use std::fmt::{Display, Formatter}; +use std::fs::read_to_string; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::sync::Arc; @@ -17,10 +18,13 @@ pub struct Config { pub(crate) source_peer_ip: IpAddr, pub(crate) keepalive_seconds: Option, pub(crate) log: String, + pub(crate) warnings: Vec, } impl Config { pub fn from_args() -> anyhow::Result { + let mut warnings = vec![]; + let matches = App::new("onetun") .author("Aram Peres ") .version(env!("CARGO_PKG_VERSION")) @@ -43,11 +47,17 @@ impl Config { \tlocalhost:8080:peer.intranet:8081:TCP\ "), Arg::with_name("private-key") - .required(true) + .required_unless("private-key-file") .takes_value(true) .long("private-key") .env("ONETUN_PRIVATE_KEY") - .help("The private key of this peer. The corresponding public key should be registered in the Wireguard endpoint."), + .help("The private key of this peer. The corresponding public key should be registered in the Wireguard endpoint. \ + You can also use '--private-key-file' to specify a file containing the key instead."), + Arg::with_name("private-key-file") + .takes_value(true) + .long("private-key-file") + .env("ONETUN_PRIVATE_KEY_FILE") + .help("The path to a file containing the private key of this peer. The corresponding public key should be registered in the Wireguard endpoint."), Arg::with_name("endpoint-public-key") .required(true) .takes_value(true) @@ -110,11 +120,38 @@ impl Config { .flatten() .collect(); + // Read private key from file or CLI argument + let (group_readable, world_readable) = matches + .value_of("private-key-file") + .map(is_file_insecurely_readable) + .flatten() + .unwrap_or_default(); + if group_readable { + warnings.push("Private key file is group-readable. This is insecure.".into()); + } + if world_readable { + warnings.push("Private key file is world-readable. This is insecure.".into()); + } + + let private_key = if let Some(private_key_file) = matches.value_of("private-key-file") { + read_to_string(private_key_file) + .map(|s| s.trim().to_string()) + .with_context(|| "Failed to read private key file") + } else { + if std::env::var("ONETUN_PRIVATE_KEY").is_err() { + warnings.push("Private key was passed using CLI. This is insecure. \ + Use \"--private-key-file \", or the \"ONETUN_PRIVATE_KEY\" env variable instead.".into()); + } + matches + .value_of("private-key") + .map(String::from) + .with_context(|| "Missing private key") + }?; + Ok(Self { port_forwards, private_key: Arc::new( - parse_private_key(matches.value_of("private-key")) - .with_context(|| "Invalid private key")?, + parse_private_key(&private_key).with_context(|| "Invalid private key")?, ), endpoint_public_key: Arc::new( parse_public_key(matches.value_of("endpoint-public-key")) @@ -127,6 +164,7 @@ impl Config { keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) .with_context(|| "Invalid keep-alive value")?, log: matches.value_of("log").unwrap_or_default().into(), + warnings, }) } } @@ -145,11 +183,9 @@ fn parse_ip(s: Option<&str>) -> anyhow::Result { .with_context(|| "Invalid IP address") } -fn parse_private_key(s: Option<&str>) -> anyhow::Result { - s.with_context(|| "Missing private key")? - .parse::() +fn parse_private_key(s: &str) -> anyhow::Result { + s.parse::() .map_err(|e| anyhow::anyhow!("{}", e)) - .with_context(|| "Invalid private key") } fn parse_public_key(s: Option<&str>) -> anyhow::Result { @@ -173,6 +209,21 @@ fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { } } +#[cfg(unix)] +fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { + use std::fs::File; + use std::os::unix::fs::MetadataExt; + + let mode = File::open(&path).ok()?.metadata().ok()?.mode(); + Some((mode & 0o40 > 0, mode & 0o4 > 0)) +} + +#[cfg(not(unix))] +fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { + // No good way to determine permissions on non-Unix target + None +} + #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct PortForwardConfig { /// The source IP and port where the local server will run. diff --git a/src/main.rs b/src/main.rs index 146a65b..d14fd06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,10 @@ async fn main() -> anyhow::Result<()> { let config = Config::from_args().with_context(|| "Failed to read config")?; init_logger(&config)?; + for warning in &config.warnings { + warn!("{}", warning); + } + // Initialize the port pool for each protocol let tcp_port_pool = TcpPortPool::new(); let udp_port_pool = UdpPortPool::new(); From 83a5d01cc8393178670c59acf33eb48fdc912aea Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 02:56:33 -0400 Subject: [PATCH 035/165] release: v0.2.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4efdeb..0369a99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,7 +620,7 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 79b6886..e692f3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.0" +version = "0.2.1" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From c50fe8233a678c0e61f6866602d7070110752238 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 26 Oct 2021 03:05:03 -0400 Subject: [PATCH 036/165] Wireguard -> WireGuard --- src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 835e05f..70ff7bb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -51,25 +51,25 @@ impl Config { .takes_value(true) .long("private-key") .env("ONETUN_PRIVATE_KEY") - .help("The private key of this peer. The corresponding public key should be registered in the Wireguard endpoint. \ + .help("The private key of this peer. The corresponding public key should be registered in the WireGuard endpoint. \ You can also use '--private-key-file' to specify a file containing the key instead."), Arg::with_name("private-key-file") .takes_value(true) .long("private-key-file") .env("ONETUN_PRIVATE_KEY_FILE") - .help("The path to a file containing the private key of this peer. The corresponding public key should be registered in the Wireguard endpoint."), + .help("The path to a file containing the private key of this peer. The corresponding public key should be registered in the WireGuard endpoint."), Arg::with_name("endpoint-public-key") .required(true) .takes_value(true) .long("endpoint-public-key") .env("ONETUN_ENDPOINT_PUBLIC_KEY") - .help("The public key of the Wireguard endpoint (remote)."), + .help("The public key of the WireGuard endpoint (remote)."), Arg::with_name("endpoint-addr") .required(true) .takes_value(true) .long("endpoint-addr") .env("ONETUN_ENDPOINT_ADDR") - .help("The address (IP + port) of the Wireguard endpoint (remote). Example: 1.2.3.4:51820"), + .help("The address (IP + port) of the WireGuard endpoint (remote). Example: 1.2.3.4:51820"), Arg::with_name("source-peer-ip") .required(true) .takes_value(true) From ff97db3b4428cf8d762b82c55f8b2d7056581669 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Thu, 28 Oct 2021 20:25:21 -0400 Subject: [PATCH 037/165] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c32bd07..02754d4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ without needing to install WireGuard in a Pod. ## Usage -**onetun** opens a TCP or UDP port on your local system, from which traffic is forwarded to a TCP port on a peer in your +**onetun** opens a TCP or UDP port on your local system, from which traffic is forwarded to a port on a peer in your WireGuard network. It requires no changes to your operating system's network interfaces: you don't need to have `root` access, or install any WireGuard tool on your local system for it to work. @@ -147,7 +147,7 @@ You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun ```shell docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ - 0.0.0.0:8080 192.168.4.2:8080 [...options...] + 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` You can also build onetun locally, using Rust: From 5ae1dca598bbb9204904505a24300c18a4df779d Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 01:26:19 +0000 Subject: [PATCH 038/165] Pin boringtun to commit --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0369a99..f9f2de3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "boringtun" version = "0.3.0" -source = "git+https://github.com/cloudflare/boringtun?branch=master#fbcf2689e7776a5af805c5a38feb5c8988829980" +source = "git+https://github.com/cloudflare/boringtun?rev=fbcf2689e7776a5af805c5a38feb5c8988829980#fbcf2689e7776a5af805c5a38feb5c8988829980" dependencies = [ "base64", "clap", diff --git a/Cargo.toml b/Cargo.toml index e692f3f..25f2c48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -boringtun = { git = "https://github.com/cloudflare/boringtun", branch = "master" } +boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "fbcf2689e7776a5af805c5a38feb5c8988829980" } clap = { version = "2.33", default-features = false, features = ["suggestions"] } log = "0.4" pretty_env_logger = "0.3" From bc39fd6306cbd00d6c2d5ec588a1f221df9206d7 Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 01:28:58 +0000 Subject: [PATCH 039/165] Pin smoltcp to commit --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9f2de3..d9e5b17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,7 +898,7 @@ checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smoltcp" version = "0.8.0" -source = "git+https://github.com/smoltcp-rs/smoltcp?branch=master#25c539bb7c96789270f032ede2a967cf0fe5cf57" +source = "git+https://github.com/smoltcp-rs/smoltcp?rev=25c539bb7c96789270f032ede2a967cf0fe5cf57#25c539bb7c96789270f032ede2a967cf0fe5cf57" dependencies = [ "bitflags", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index 25f2c48..0018d4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ clap = { version = "2.33", default-features = false, features = ["suggestions"] log = "0.4" pretty_env_logger = "0.3" anyhow = "1" -smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", branch = "master" } +smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", rev = "25c539bb7c96789270f032ede2a967cf0fe5cf57" } tokio = { version = "1", features = ["full"] } futures = "0.3.17" rand = "0.8.4" From 201d7c1ee6bcfaf355dd127d74a95a390844d6d8 Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 01:39:23 +0000 Subject: [PATCH 040/165] Run cargo update --- Cargo.lock | 151 +++++++++++++++++++++++------------------------------ 1 file changed, 66 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9e5b17..0906fdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.44" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "ascii" @@ -40,9 +40,9 @@ checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -68,9 +68,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" dependencies = [ "addr2line", "cc", @@ -121,9 +121,9 @@ checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" [[package]] name = "bumpalo" -version = "3.7.1" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" [[package]] name = "byteorder" @@ -139,9 +139,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cesu8" @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "strsim", @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" dependencies = [ "futures-channel", "futures-core", @@ -280,9 +280,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" dependencies = [ "futures-core", "futures-sink", @@ -290,15 +290,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" [[package]] name = "futures-executor" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" dependencies = [ "futures-core", "futures-task", @@ -307,18 +307,16 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" [[package]] name = "futures-macro" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ - "autocfg", - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -326,23 +324,22 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" [[package]] name = "futures-task" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" [[package]] name = "futures-util" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" dependencies = [ - "autocfg", "futures-channel", "futures-core", "futures-io", @@ -352,8 +349,6 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] @@ -370,9 +365,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "hashbrown" @@ -416,9 +411,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] @@ -482,9 +477,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.103" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "lock_api" @@ -518,9 +513,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "minimal-lexical" -version = "0.1.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" @@ -534,9 +529,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", @@ -556,9 +551,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ "memchr", "minimal-lexical", @@ -605,18 +600,18 @@ dependencies = [ [[package]] name = "object" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" @@ -676,9 +671,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "pretty_env_logger" @@ -693,31 +688,19 @@ dependencies = [ [[package]] name = "priority-queue" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf40e51ccefb72d42720609e1d3c518de8b5800d723a09358d4a6d6245e1f8ca" +checksum = "00ba480ac08d3cfc40dea10fd466fd2c14dee3ea6fc7873bc4079eda2727caf0" dependencies = [ "autocfg", "indexmap", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - [[package]] name = "proc-macro2" -version = "1.0.29" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" dependencies = [ "unicode-xid", ] @@ -836,9 +819,9 @@ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustversion" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "same-file" @@ -922,9 +905,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", @@ -971,22 +954,20 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "wasi", "winapi", ] [[package]] name = "tokio" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" dependencies = [ - "autocfg", "bytes", "libc", "memchr", @@ -1002,9 +983,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -1063,9 +1044,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" From 67610929bcbd3fa7f46fcfd6fe3feb743741b2b4 Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 01:41:02 +0000 Subject: [PATCH 041/165] Remove build-binary feaure from boringtun --- Cargo.lock | 89 ------------------------------------------------------ Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0906fdf..e804322 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,8 +99,6 @@ version = "0.3.0" source = "git+https://github.com/cloudflare/boringtun?rev=fbcf2689e7776a5af805c5a38feb5c8988829980#fbcf2689e7776a5af805c5a38feb5c8988829980" dependencies = [ "base64", - "clap", - "daemonize", "hex", "ip_network", "ip_network_table", @@ -109,16 +107,9 @@ dependencies = [ "parking_lot", "ring", "slog", - "slog-term", "untrusted", ] -[[package]] -name = "boxfnonce" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" - [[package]] name = "bumpalo" version = "3.8.0" @@ -193,16 +184,6 @@ dependencies = [ "unreachable", ] -[[package]] -name = "daemonize" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815" -dependencies = [ - "boxfnonce", - "libc", -] - [[package]] name = "dashmap" version = "4.0.2" @@ -213,27 +194,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "either" version = "1.6.1" @@ -769,16 +729,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom", - "redox_syscall", -] - [[package]] name = "regex" version = "1.5.4" @@ -817,12 +767,6 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - [[package]] name = "same-file" version = "1.0.6" @@ -859,19 +803,6 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" -[[package]] -name = "slog-term" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c1e7e5aab61ced6006149ea772770b84a0d16ce0f7885def313e4829946d76" -dependencies = [ - "atty", - "chrono", - "slog", - "term", - "thread_local", -] - [[package]] name = "smallvec" version = "1.7.0" @@ -914,17 +845,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - [[package]] name = "termcolor" version = "1.1.2" @@ -943,15 +863,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "thread_local" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" -dependencies = [ - "once_cell", -] - [[package]] name = "time" version = "0.1.43" diff --git a/Cargo.toml b/Cargo.toml index 0018d4b..013c852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "fbcf2689e7776a5af805c5a38feb5c8988829980" } +boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "fbcf2689e7776a5af805c5a38feb5c8988829980", default-features = false } clap = { version = "2.33", default-features = false, features = ["suggestions"] } log = "0.4" pretty_env_logger = "0.3" From 40d7c18c853780c0d05582e7d887c2390dd172d0 Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 01:42:33 +0000 Subject: [PATCH 042/165] Update pretty_env_logger to 0.4.0 --- Cargo.lock | 51 ++++----------------------------------------------- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e804322..542b73d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,19 +146,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time", - "winapi", -] - [[package]] name = "clap" version = "2.34.0" @@ -202,9 +189,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "env_logger" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime", @@ -529,25 +516,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" version = "1.13.0" @@ -637,11 +605,10 @@ checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "pretty_env_logger" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ - "chrono", "env_logger", "log", ] @@ -863,16 +830,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "tokio" version = "1.15.0" diff --git a/Cargo.toml b/Cargo.toml index 013c852..5efad48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "fbcf2689e7776a5af805c5a38feb5c8988829980", default-features = false } clap = { version = "2.33", default-features = false, features = ["suggestions"] } log = "0.4" -pretty_env_logger = "0.3" +pretty_env_logger = "0.4" anyhow = "1" smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", rev = "25c539bb7c96789270f032ede2a967cf0fe5cf57" } tokio = { version = "1", features = ["full"] } From af803ffc6a6a548a49712afd6b892e7ce7b49aa9 Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 02:16:40 +0000 Subject: [PATCH 043/165] Update to smoltcp 0.8.0 --- Cargo.lock | 3 ++- Cargo.toml | 2 +- src/ip_sink.rs | 8 +++----- src/virtual_iface/tcp.rs | 23 ++++++++++++----------- src/virtual_iface/udp.rs | 19 +++++++++---------- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 542b73d..2b8ed7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,7 +779,8 @@ checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smoltcp" version = "0.8.0" -source = "git+https://github.com/smoltcp-rs/smoltcp?rev=25c539bb7c96789270f032ede2a967cf0fe5cf57#25c539bb7c96789270f032ede2a967cf0fe5cf57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2308a1657c8db1f5b4993bab4e620bdbe5623bd81f254cf60326767bb243237" dependencies = [ "bitflags", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index 5efad48..50dd3c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ clap = { version = "2.33", default-features = false, features = ["suggestions"] log = "0.4" pretty_env_logger = "0.4" anyhow = "1" -smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", rev = "25c539bb7c96789270f032ede2a967cf0fe5cf57" } +smoltcp = "0.8.0" tokio = { version = "1", features = ["full"] } futures = "0.3.17" rand = "0.8.4" diff --git a/src/ip_sink.rs b/src/ip_sink.rs index f17eb21..a209da9 100644 --- a/src/ip_sink.rs +++ b/src/ip_sink.rs @@ -1,7 +1,6 @@ use crate::virtual_device::VirtualIpDevice; use crate::wg::WireGuardTunnel; use smoltcp::iface::InterfaceBuilder; -use smoltcp::socket::SocketSet; use std::sync::Arc; use tokio::time::Duration; @@ -13,13 +12,12 @@ pub async fn run_ip_sink_interface(wg: Arc) -> ! { .expect("Failed to initialize VirtualIpDevice for sink interface"); // No sockets on sink interface - let mut socket_set_entries: [_; 0] = Default::default(); - let mut socket_set = SocketSet::new(&mut socket_set_entries[..]); - let mut virtual_interface = InterfaceBuilder::new(device).ip_addrs([]).finalize(); + let mut sockets: [_; 0] = Default::default(); + let mut virtual_interface = InterfaceBuilder::new(device, &mut sockets[..]).ip_addrs([]).finalize(); loop { let loop_start = smoltcp::time::Instant::now(); - match virtual_interface.poll(&mut socket_set, loop_start) { + match virtual_interface.poll(loop_start) { Ok(processed) if processed => { trace!("[SINK] Virtual interface polled some packets to be processed",); tokio::time::sleep(Duration::from_millis(1)).await; diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index a632792..d34357a 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -5,7 +5,7 @@ use crate::wg::WireGuardTunnel; use anyhow::Context; use async_trait::async_trait; use smoltcp::iface::InterfaceBuilder; -use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer, TcpState}; +use smoltcp::socket::{TcpSocket, TcpSocketBuffer, TcpState}; use smoltcp::wire::{IpAddress, IpCidr}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -77,7 +77,10 @@ impl VirtualInterfacePoll for TcpVirtualInterface { let device = VirtualIpDevice::new_direct(VirtualPort(self.virtual_port, PortProtocol::Tcp), self.wg) .with_context(|| "Failed to initialize TCP VirtualIpDevice")?; - let mut virtual_interface = InterfaceBuilder::new(device) + + // there are always 2 sockets: 1 virtual client and 1 virtual server. + let mut sockets: [_; 2] = Default::default(); + let mut virtual_interface = InterfaceBuilder::new(device, &mut sockets[..]) .ip_addrs([ // Interface handles IP packets for the sender and recipient IpCidr::new(IpAddress::from(source_peer_ip), 32), @@ -112,11 +115,8 @@ impl VirtualInterfacePoll for TcpVirtualInterface { Ok(socket) }; - // Socket set: there are always 2 sockets: 1 virtual client and 1 virtual server. - let mut socket_set_entries: [_; 2] = Default::default(); - let mut socket_set = SocketSet::new(&mut socket_set_entries[..]); - let _server_handle = socket_set.add(server_socket?); - let client_handle = socket_set.add(client_socket?); + let _server_handle = virtual_interface.add_socket(server_socket?); + let client_handle = virtual_interface.add_socket(client_socket?); // Any data that wasn't sent because it was over the sending buffer limit let mut tx_extra = Vec::new(); @@ -137,11 +137,11 @@ impl VirtualInterfacePoll for TcpVirtualInterface { if shutdown { // Shutdown: sends a RST packet. trace!("[{}] Shutting down virtual interface", self.virtual_port); - let mut client_socket = socket_set.get::(client_handle); + let client_socket = virtual_interface.get_socket::(client_handle); client_socket.abort(); } - match virtual_interface.poll(&mut socket_set, loop_start) { + match virtual_interface.poll(loop_start) { Ok(processed) if processed => { trace!( "[{}] Virtual interface polled some packets to be processed", @@ -158,7 +158,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } { - let mut client_socket = socket_set.get::(client_handle); + let (client_socket, context) = virtual_interface.get_socket_and_context::(client_handle); if !shutdown && client_socket.state() == TcpState::Closed && !has_connected { // Not shutting down, but the client socket is closed, and the client never successfully connected. @@ -166,6 +166,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { // Try to connect client_socket .connect( + context, ( IpAddress::from(self.port_forward.destination.ip()), self.port_forward.destination.port(), @@ -266,7 +267,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { break; } - match virtual_interface.poll_delay(&socket_set, loop_start) { + match virtual_interface.poll_delay(loop_start) { Some(smoltcp::time::Duration::ZERO) => { continue; } diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 212cb8d..1fdbb63 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -4,8 +4,8 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; -use smoltcp::iface::InterfaceBuilder; -use smoltcp::socket::{SocketHandle, SocketSet, UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; +use smoltcp::iface::{InterfaceBuilder, SocketHandle}; +use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; use smoltcp::wire::{IpAddress, IpCidr}; use crate::config::PortForwardConfig; @@ -56,7 +56,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { let (base_ip_dispatch_tx, ip_dispatch_rx) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); let device = VirtualIpDevice::new(self.wg.clone(), ip_dispatch_rx); - let mut virtual_interface = InterfaceBuilder::new(device) + let mut virtual_interface = InterfaceBuilder::new(device, vec![]) .ip_addrs([ // Interface handles IP packets for the sender and recipient IpCidr::new(source_peer_ip.into(), 32), @@ -87,8 +87,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { Ok(socket) }; - let mut socket_set = SocketSet::new(vec![]); - let _server_handle = socket_set.add(server_socket?); + let _server_handle = virtual_interface.add_socket(server_socket?); // A map of virtual port to client socket. let mut client_sockets: HashMap = HashMap::new(); @@ -107,7 +106,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } => { let loop_start = smoltcp::time::Instant::now(); - match virtual_interface.poll(&mut socket_set, loop_start) { + match virtual_interface.poll(loop_start) { Ok(processed) if processed => { trace!("UDP virtual interface polled some packets to be processed"); } @@ -118,7 +117,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { // Loop through each client socket and check if there is any data to send back // to the real client. for (virtual_port, client_socket_handle) in client_sockets.iter() { - let mut client_socket = socket_set.get::(*client_socket_handle); + let client_socket = virtual_interface.get_socket::(*client_socket_handle); match client_socket.recv() { Ok((data, _peer)) => { // Send the data back to the real client using MPSC channel @@ -142,7 +141,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } } - next_poll = match virtual_interface.poll_delay(&socket_set, loop_start) { + next_poll = match virtual_interface.poll_delay(loop_start) { Some(smoltcp::time::Duration::ZERO) => None, Some(delay) => Some(tokio::time::Instant::now() + Duration::from_millis(delay.millis())), None => None, @@ -178,10 +177,10 @@ impl VirtualInterfacePoll for UdpVirtualInterface { ); }); - socket_set.add(socket) + virtual_interface.add_socket(socket) }); - let mut client_socket = socket_set.get::(*client_socket_handle); + let client_socket = virtual_interface.get_socket::(*client_socket_handle); client_socket .send_slice( &data, From f270e64a5cd8599d21f4fb1110867a9d624cdf22 Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 02:31:48 +0000 Subject: [PATCH 044/165] Disable not needed features in smoltcp --- Cargo.lock | 1 - Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b8ed7f..a010a8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,7 +784,6 @@ checksum = "d2308a1657c8db1f5b4993bab4e620bdbe5623bd81f254cf60326767bb243237" dependencies = [ "bitflags", "byteorder", - "libc", "log", "managed", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 50dd3c4..f145159 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ clap = { version = "2.33", default-features = false, features = ["suggestions"] log = "0.4" pretty_env_logger = "0.4" anyhow = "1" -smoltcp = "0.8.0" +smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } tokio = { version = "1", features = ["full"] } futures = "0.3.17" rand = "0.8.4" From d51144b693479f8821c1efe8eb9576287a4607bd Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 20 Dec 2021 02:19:54 -0500 Subject: [PATCH 045/165] Formatting --- src/ip_sink.rs | 4 +++- src/virtual_iface/tcp.rs | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ip_sink.rs b/src/ip_sink.rs index a209da9..b9226f7 100644 --- a/src/ip_sink.rs +++ b/src/ip_sink.rs @@ -13,7 +13,9 @@ pub async fn run_ip_sink_interface(wg: Arc) -> ! { // No sockets on sink interface let mut sockets: [_; 0] = Default::default(); - let mut virtual_interface = InterfaceBuilder::new(device, &mut sockets[..]).ip_addrs([]).finalize(); + let mut virtual_interface = InterfaceBuilder::new(device, &mut sockets[..]) + .ip_addrs([]) + .finalize(); loop { let loop_start = smoltcp::time::Instant::now(); diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index d34357a..340242a 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -77,7 +77,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { let device = VirtualIpDevice::new_direct(VirtualPort(self.virtual_port, PortProtocol::Tcp), self.wg) .with_context(|| "Failed to initialize TCP VirtualIpDevice")?; - + // there are always 2 sockets: 1 virtual client and 1 virtual server. let mut sockets: [_; 2] = Default::default(); let mut virtual_interface = InterfaceBuilder::new(device, &mut sockets[..]) @@ -158,7 +158,8 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } { - let (client_socket, context) = virtual_interface.get_socket_and_context::(client_handle); + let (client_socket, context) = + virtual_interface.get_socket_and_context::(client_handle); if !shutdown && client_socket.state() == TcpState::Closed && !has_connected { // Not shutting down, but the client socket is closed, and the client never successfully connected. From 81264916e5436d92046d24e0223f8edda99f6804 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 20 Dec 2021 02:27:18 -0500 Subject: [PATCH 046/165] release: v0.2.2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a010a8f..1b39f99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,7 +543,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.1" +version = "0.2.2" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index f145159..83ad9a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.1" +version = "0.2.2" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 23af49dde5529d4d13b755089f53f773f4dbbf83 Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 17:03:44 +0000 Subject: [PATCH 047/165] Allow configuration of max transmission unit --- src/config.rs | 16 ++++++++++++++++ src/virtual_device.rs | 5 +---- src/wg.rs | 3 +++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 70ff7bb..ed6757c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,7 @@ pub struct Config { pub(crate) endpoint_addr: SocketAddr, pub(crate) source_peer_ip: IpAddr, pub(crate) keepalive_seconds: Option, + pub(crate) max_transmission_unit: usize, pub(crate) log: String, pub(crate) warnings: Vec, } @@ -82,6 +83,13 @@ impl Config { .long("keep-alive") .env("ONETUN_KEEP_ALIVE") .help("Configures a persistent keep-alive for the WireGuard tunnel, in seconds."), + Arg::with_name("max-transmission-unit") + .required(false) + .takes_value(true) + .long("max-transmission-unit") + .env("ONETUN_MTU") + .default_value("1420") + .help("Configures the max-transmission-unit (MTU) of the WireGuard tunnel."), Arg::with_name("log") .required(false) .takes_value(true) @@ -163,6 +171,8 @@ impl Config { .with_context(|| "Invalid source peer IP")?, keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) .with_context(|| "Invalid keep-alive value")?, + max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit")) + .with_context(|| "Invalid max-transmission-unit value")?, log: matches.value_of("log").unwrap_or_default().into(), warnings, }) @@ -209,6 +219,12 @@ fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { } } +fn parse_mtu(s: Option<&str>) -> anyhow::Result { + s.with_context(|| "Missing MTU")? + .parse() + .with_context(|| "Invalid MTU") +} + #[cfg(unix)] fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { use std::fs::File; diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 02d60f9..d06d7fa 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -5,9 +5,6 @@ use smoltcp::phy::{Device, DeviceCapabilities, Medium}; use smoltcp::time::Instant; use std::sync::Arc; -/// The max transmission unit for WireGuard. -const WG_MTU: usize = 1420; - /// A virtual device that processes IP packets. IP packets received from the WireGuard endpoint /// are made available to this device using a channel receiver. IP packets sent from this device /// are asynchronously sent out to the WireGuard tunnel. @@ -71,7 +68,7 @@ impl<'a> Device<'a> for VirtualIpDevice { fn capabilities(&self) -> DeviceCapabilities { let mut cap = DeviceCapabilities::default(); cap.medium = Medium::Ip; - cap.max_transmission_unit = WG_MTU; + cap.max_transmission_unit = self.wg.max_transmission_unit; cap } } diff --git a/src/wg.rs b/src/wg.rs index 2dc20d3..ca701f3 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -30,6 +30,8 @@ pub struct WireGuardTunnel { virtual_port_ip_tx: dashmap::DashMap>>, /// IP packet dispatcher for unroutable packets. `None` if not initialized. sink_ip_tx: RwLock>>>, + /// The max transmission unit for WireGuard. + pub(crate) max_transmission_unit: usize, } impl WireGuardTunnel { @@ -50,6 +52,7 @@ impl WireGuardTunnel { endpoint, virtual_port_ip_tx, sink_ip_tx: RwLock::new(None), + max_transmission_unit: config.max_transmission_unit, }) } From ce9cfce8fca83255704d8c4c382667b24d3e0afe Mon Sep 17 00:00:00 2001 From: Tilo Spannagel Date: Mon, 20 Dec 2021 17:37:57 +0000 Subject: [PATCH 048/165] Fix ipv6 endpoints --- src/wg.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/wg.rs b/src/wg.rs index 2dc20d3..305232f 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -37,10 +37,13 @@ impl WireGuardTunnel { pub async fn new(config: &Config) -> anyhow::Result { let source_peer_ip = config.source_peer_ip; let peer = Self::create_tunnel(config)?; - let udp = UdpSocket::bind("0.0.0.0:0") - .await - .with_context(|| "Failed to create UDP socket for WireGuard connection")?; let endpoint = config.endpoint_addr; + let udp = UdpSocket::bind(match endpoint { + SocketAddr::V4(_) => "0.0.0.0:0", + SocketAddr::V6(_) => "[::]:0", + }) + .await + .with_context(|| "Failed to create UDP socket for WireGuard connection")?; let virtual_port_ip_tx = Default::default(); Ok(Self { From 62b2641627a62d02a63e6125af2d0381d3245b7b Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 21 Dec 2021 03:43:15 -0500 Subject: [PATCH 049/165] release: v0.2.3 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b39f99..f1d4364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,7 +543,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.2" +version = "0.2.3" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 83ad9a3..accb4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.2" +version = "0.2.3" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 51788c95577109082b2f73cda3587bd910a2b040 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 01:05:51 -0500 Subject: [PATCH 050/165] Improve reliability using event-based synchronization --- src/config.rs | 5 +- src/events.rs | 150 ++++++++++++++ src/ip_sink.rs | 35 ---- src/main.rs | 65 +++++- src/tunnel/mod.rs | 6 +- src/tunnel/tcp.rs | 139 ++++--------- src/tunnel/udp.rs | 75 ++----- src/virtual_device.rs | 98 ++++----- src/virtual_iface/mod.rs | 45 +++- src/virtual_iface/tcp.rs | 433 ++++++++++++++++++--------------------- src/virtual_iface/udp.rs | 201 +++--------------- src/wg.rs | 181 +++------------- 12 files changed, 628 insertions(+), 805 deletions(-) create mode 100644 src/events.rs delete mode 100644 src/ip_sink.rs diff --git a/src/config.rs b/src/config.rs index ed6757c..8df9511 100644 --- a/src/config.rs +++ b/src/config.rs @@ -235,7 +235,7 @@ fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { } #[cfg(not(unix))] -fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { +fn is_file_insecurely_readable(_path: &str) -> Option<(bool, bool)> { // No good way to determine permissions on non-Unix target None } @@ -399,9 +399,12 @@ impl Display for PortForwardConfig { } } +/// Layer 7 protocols for ports. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum PortProtocol { + /// TCP Tcp, + /// UDP Udp, } diff --git a/src/events.rs b/src/events.rs new file mode 100644 index 0000000..1437bc2 --- /dev/null +++ b/src/events.rs @@ -0,0 +1,150 @@ +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; + +use crate::config::PortForwardConfig; +use crate::virtual_iface::VirtualPort; +use crate::PortProtocol; + +/// Events that go on the bus between the local server, smoltcp, and WireGuard. +#[derive(Debug, Clone)] +pub enum Event { + /// Dumb event with no data. + Dumb, + /// A new connection with the local server was initiated, and the given virtual port was assigned. + ClientConnectionInitiated(PortForwardConfig, VirtualPort), + /// A connection was dropped from the pool and should be closed in all interfaces. + ClientConnectionDropped(VirtualPort), + /// Data received by the local server that should be sent to the virtual server. + LocalData(VirtualPort, Vec), + /// Data received by the remote server that should be sent to the local client. + RemoteData(VirtualPort, Vec), + /// IP packet received from the WireGuard tunnel that should be passed through the corresponding virtual device. + InboundInternetPacket(PortProtocol, Vec), + /// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device. + OutboundInternetPacket(Vec), + /// Notifies that a virtual device read an IP packet. + VirtualDeviceFed(PortProtocol), +} + +#[derive(Clone)] +pub struct Bus { + counter: Arc, + bus: Arc>, +} + +impl Bus { + /// Creates a new event bus. + pub fn new() -> Self { + let (bus, _) = tokio::sync::broadcast::channel(1000); + let bus = Arc::new(bus); + let counter = Arc::new(AtomicU32::default()); + Self { bus, counter } + } + + /// Creates a new endpoint on the event bus. + pub fn new_endpoint(&self) -> BusEndpoint { + let id = self.counter.fetch_add(1, Ordering::Relaxed); + let tx = (*self.bus).clone(); + let rx = self.bus.subscribe(); + + let tx = BusSender { id, tx }; + BusEndpoint { id, tx, rx } + } +} + +impl Default for Bus { + fn default() -> Self { + Self::new() + } +} + +pub struct BusEndpoint { + id: u32, + tx: BusSender, + rx: tokio::sync::broadcast::Receiver<(u32, Event)>, +} + +impl BusEndpoint { + /// Sends the event on the bus. Note that the messages sent by this endpoint won't reach itself. + pub fn send(&self, event: Event) { + self.tx.send(event) + } + + /// Returns the unique sequential ID of this endpoint. + pub fn id(&self) -> u32 { + self.id + } + + /// Awaits the next `Event` on the bus to be read. + pub async fn recv(&mut self) -> Event { + loop { + match self.rx.recv().await { + Ok((id, event)) => { + if id == self.id { + // If the event was sent by this endpoint, it is skipped + continue; + } else { + trace!("#{} <- {:?}", self.id, event); + return event; + } + } + Err(_) => { + error!("Failed to read event bus from endpoint #{}", self.id); + return futures::future::pending().await; + } + } + } + } + + /// Creates a new sender for this endpoint that can be cloned. + pub fn sender(&self) -> BusSender { + self.tx.clone() + } +} + +#[derive(Clone)] +pub struct BusSender { + id: u32, + tx: tokio::sync::broadcast::Sender<(u32, Event)>, +} + +impl BusSender { + /// Sends the event on the bus. Note that the messages sent by this endpoint won't reach itself. + pub fn send(&self, event: Event) { + trace!("#{} -> {:?}", self.id, event); + match self.tx.send((self.id, event)) { + Ok(_) => {} + Err(_) => error!("Failed to send event to bus from endpoint #{}", self.id), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_bus() { + let bus = Bus::new(); + + let mut endpoint_1 = bus.new_endpoint(); + let mut endpoint_2 = bus.new_endpoint(); + let mut endpoint_3 = bus.new_endpoint(); + + assert_eq!(endpoint_1.id(), 0); + assert_eq!(endpoint_2.id(), 1); + assert_eq!(endpoint_3.id(), 2); + + endpoint_1.send(Event::Dumb); + let recv_2 = endpoint_2.recv().await; + let recv_3 = endpoint_3.recv().await; + assert!(matches!(recv_2, Event::Dumb)); + assert!(matches!(recv_3, Event::Dumb)); + + endpoint_2.send(Event::Dumb); + let recv_1 = endpoint_1.recv().await; + let recv_3 = endpoint_3.recv().await; + assert!(matches!(recv_1, Event::Dumb)); + assert!(matches!(recv_3, Event::Dumb)); + } +} diff --git a/src/ip_sink.rs b/src/ip_sink.rs deleted file mode 100644 index b9226f7..0000000 --- a/src/ip_sink.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::virtual_device::VirtualIpDevice; -use crate::wg::WireGuardTunnel; -use smoltcp::iface::InterfaceBuilder; -use std::sync::Arc; -use tokio::time::Duration; - -/// A repeating task that processes unroutable IP packets. -pub async fn run_ip_sink_interface(wg: Arc) -> ! { - // Initialize interface - let device = VirtualIpDevice::new_sink(wg) - .await - .expect("Failed to initialize VirtualIpDevice for sink interface"); - - // No sockets on sink interface - let mut sockets: [_; 0] = Default::default(); - let mut virtual_interface = InterfaceBuilder::new(device, &mut sockets[..]) - .ip_addrs([]) - .finalize(); - - loop { - let loop_start = smoltcp::time::Instant::now(); - match virtual_interface.poll(loop_start) { - Ok(processed) if processed => { - trace!("[SINK] Virtual interface polled some packets to be processed",); - tokio::time::sleep(Duration::from_millis(1)).await; - } - Err(e) => { - error!("[SINK] Virtual interface poll error: {:?}", e); - } - _ => { - tokio::time::sleep(Duration::from_millis(5)).await; - } - } - } -} diff --git a/src/main.rs b/src/main.rs index d14fd06..1a01b51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,18 @@ use std::sync::Arc; use anyhow::Context; -use crate::config::Config; +use crate::config::{Config, PortProtocol}; +use crate::events::Bus; use crate::tunnel::tcp::TcpPortPool; use crate::tunnel::udp::UdpPortPool; +use crate::virtual_device::VirtualIpDevice; +use crate::virtual_iface::tcp::TcpVirtualInterface; +use crate::virtual_iface::udp::UdpVirtualInterface; +use crate::virtual_iface::VirtualInterfacePoll; use crate::wg::WireGuardTunnel; pub mod config; -pub mod ip_sink; +pub mod events; pub mod tunnel; pub mod virtual_device; pub mod virtual_iface; @@ -30,7 +35,9 @@ async fn main() -> anyhow::Result<()> { let tcp_port_pool = TcpPortPool::new(); let udp_port_pool = UdpPortPool::new(); - let wg = WireGuardTunnel::new(&config) + let bus = Bus::default(); + + let wg = WireGuardTunnel::new(&config, bus.clone()) .await .with_context(|| "Failed to initialize WireGuard tunnel")?; let wg = Arc::new(wg); @@ -48,9 +55,41 @@ async fn main() -> anyhow::Result<()> { } { - // Start IP sink task for incoming IP packets + // Start production task for WireGuard let wg = wg.clone(); - tokio::spawn(async move { ip_sink::run_ip_sink_interface(wg).await }); + tokio::spawn(async move { wg.produce_task().await }); + } + + if config + .port_forwards + .iter() + .any(|pf| pf.protocol == PortProtocol::Tcp) + { + // TCP device + let bus = bus.clone(); + let device = + VirtualIpDevice::new(PortProtocol::Tcp, bus.clone(), config.max_transmission_unit); + + // Start TCP Virtual Interface + let port_forwards = config.port_forwards.clone(); + let iface = TcpVirtualInterface::new(port_forwards, bus, device, config.source_peer_ip); + tokio::spawn(async move { iface.poll_loop().await }); + } + + if config + .port_forwards + .iter() + .any(|pf| pf.protocol == PortProtocol::Udp) + { + // UDP device + let bus = bus.clone(); + let device = + VirtualIpDevice::new(PortProtocol::Udp, bus.clone(), config.max_transmission_unit); + + // Start UDP Virtual Interface + let port_forwards = config.port_forwards.clone(); + let iface = UdpVirtualInterface::new(port_forwards, bus, device, config.source_peer_ip); + tokio::spawn(async move { iface.poll_loop().await }); } { @@ -59,10 +98,18 @@ async fn main() -> anyhow::Result<()> { port_forwards .into_iter() - .map(|pf| (pf, wg.clone(), tcp_port_pool.clone(), udp_port_pool.clone())) - .for_each(move |(pf, wg, tcp_port_pool, udp_port_pool)| { + .map(|pf| { + ( + pf, + wg.clone(), + tcp_port_pool.clone(), + udp_port_pool.clone(), + bus.clone(), + ) + }) + .for_each(move |(pf, wg, tcp_port_pool, udp_port_pool, bus)| { tokio::spawn(async move { - tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg) + tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg, bus) .await .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) }); @@ -73,7 +120,7 @@ async fn main() -> anyhow::Result<()> { } fn init_logger(config: &Config) -> anyhow::Result<()> { - let mut builder = pretty_env_logger::formatted_builder(); + let mut builder = pretty_env_logger::formatted_timed_builder(); builder.parse_filters(&config.log); builder .try_init() diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index c7f2a67..1676d7a 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -2,6 +2,7 @@ use std::net::IpAddr; use std::sync::Arc; use crate::config::{PortForwardConfig, PortProtocol}; +use crate::events::Bus; use crate::tunnel::tcp::TcpPortPool; use crate::tunnel::udp::UdpPortPool; use crate::wg::WireGuardTunnel; @@ -16,6 +17,7 @@ pub async fn port_forward( tcp_port_pool: TcpPortPool, udp_port_pool: UdpPortPool, wg: Arc, + bus: Bus, ) -> anyhow::Result<()> { info!( "Tunneling {} [{}]->[{}] (via [{}] as peer {})", @@ -27,7 +29,7 @@ pub async fn port_forward( ); match port_forward.protocol { - PortProtocol::Tcp => tcp::tcp_proxy_server(port_forward, tcp_port_pool, wg).await, - PortProtocol::Udp => udp::udp_proxy_server(port_forward, udp_port_pool, wg).await, + PortProtocol::Tcp => tcp::tcp_proxy_server(port_forward, tcp_port_pool, bus).await, + PortProtocol::Udp => udp::udp_proxy_server(port_forward, udp_port_pool, bus).await, } } diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index f49aa7c..718cf57 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -1,17 +1,17 @@ use crate::config::{PortForwardConfig, PortProtocol}; -use crate::virtual_iface::tcp::TcpVirtualInterface; -use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; -use crate::wg::WireGuardTunnel; +use crate::virtual_iface::VirtualPort; use anyhow::Context; use std::collections::VecDeque; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::net::{TcpListener, TcpStream}; use std::ops::Range; +use std::time::Duration; +use crate::events::{Bus, Event}; use rand::seq::SliceRandom; use rand::thread_rng; +use tokio::io::AsyncWriteExt; const MAX_PACKET: usize = 65536; const MIN_PORT: u16 = 1000; @@ -22,14 +22,13 @@ const PORT_RANGE: Range = MIN_PORT..MAX_PORT; pub async fn tcp_proxy_server( port_forward: PortForwardConfig, port_pool: TcpPortPool, - wg: Arc, + bus: Bus, ) -> anyhow::Result<()> { let listener = TcpListener::bind(port_forward.source) .await .with_context(|| "Failed to listen on TCP proxy server")?; loop { - let wg = wg.clone(); let port_pool = port_pool.clone(); let (socket, peer_addr) = listener .accept() @@ -52,10 +51,10 @@ pub async fn tcp_proxy_server( info!("[{}] Incoming connection from {}", virtual_port, peer_addr); + let bus = bus.clone(); tokio::spawn(async move { let port_pool = port_pool.clone(); - let result = - handle_tcp_proxy_connection(socket, virtual_port, port_forward, wg.clone()).await; + let result = handle_tcp_proxy_connection(socket, virtual_port, port_forward, bus).await; if let Err(e) = result { error!( @@ -66,8 +65,7 @@ pub async fn tcp_proxy_server( info!("[{}] Connection closed by client", virtual_port); } - // Release port when connection drops - wg.release_virtual_interface(VirtualPort(virtual_port, PortProtocol::Tcp)); + tokio::time::sleep(Duration::from_millis(100)).await; // Make sure the other tasks have time to process the event port_pool.release(virtual_port).await; }); } @@ -75,72 +73,26 @@ pub async fn tcp_proxy_server( /// Handles a new TCP connection with its assigned virtual port. async fn handle_tcp_proxy_connection( - socket: TcpStream, - virtual_port: u16, + mut socket: TcpStream, + virtual_port: VirtualPort, port_forward: PortForwardConfig, - wg: Arc, + bus: Bus, ) -> anyhow::Result<()> { - // Abort signal for stopping the Virtual Interface - let abort = Arc::new(AtomicBool::new(false)); - - // Signals that the Virtual Client is ready to send data - let (virtual_client_ready_tx, virtual_client_ready_rx) = tokio::sync::oneshot::channel::<()>(); - - // data_to_real_client_(tx/rx): This task reads the data from this mpsc channel to send back - // to the real client. - let (data_to_real_client_tx, mut data_to_real_client_rx) = tokio::sync::mpsc::channel(1_000); - - // data_to_real_server_(tx/rx): This task sends the data received from the real client to the - // virtual interface (virtual server socket). - let (data_to_virtual_server_tx, data_to_virtual_server_rx) = tokio::sync::mpsc::channel(1_000); - - // Spawn virtual interface - { - let abort = abort.clone(); - let virtual_interface = TcpVirtualInterface::new( - virtual_port, - port_forward, - wg, - abort.clone(), - data_to_real_client_tx, - data_to_virtual_server_rx, - virtual_client_ready_tx, - ); - - tokio::spawn(async move { - virtual_interface.poll_loop().await.unwrap_or_else(|e| { - error!("Virtual interface poll loop failed unexpectedly: {}", e); - abort.store(true, Ordering::Relaxed); - }) - }); - } - - // Wait for virtual client to be ready. - virtual_client_ready_rx - .await - .with_context(|| "Virtual client dropped before being ready.")?; - trace!("[{}] Virtual client is ready to send data", virtual_port); + let mut endpoint = bus.new_endpoint(); + endpoint.send(Event::ClientConnectionInitiated(port_forward, virtual_port)); + let mut buffer = Vec::with_capacity(MAX_PACKET); loop { tokio::select! { readable_result = socket.readable() => { match readable_result { Ok(_) => { - // Buffer for the individual TCP segment. - let mut buffer = Vec::with_capacity(MAX_PACKET); match socket.try_read_buf(&mut buffer) { Ok(size) if size > 0 => { - let data = &buffer[..size]; - debug!( - "[{}] Read {} bytes of TCP data from real client", - virtual_port, size - ); - if let Err(e) = data_to_virtual_server_tx.send(data.to_vec()).await { - error!( - "[{}] Failed to dispatch data to virtual interface: {:?}", - virtual_port, e - ); - } + let data = Vec::from(&buffer[..size]); + endpoint.send(Event::LocalData(virtual_port, data)); + // Reset buffer + buffer.clear(); } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { continue; @@ -163,43 +115,32 @@ async fn handle_tcp_proxy_connection( } } } - data_recv_result = data_to_real_client_rx.recv() => { - match data_recv_result { - Some(data) => match socket.try_write(&data) { - Ok(size) => { - debug!( - "[{}] Wrote {} bytes of TCP data to real client", - virtual_port, size - ); - } - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - if abort.load(Ordering::Relaxed) { + event = endpoint.recv() => { + match event { + Event::ClientConnectionDropped(e_vp) if e_vp == virtual_port => { + // This connection is supposed to be closed, stop the task. + break; + } + Event::RemoteData(e_vp, data) if e_vp == virtual_port => { + // Have remote data to send to the local client + let size = data.len(); + match socket.write(&data).await { + Ok(size) => debug!("[{}] Sent {} bytes to local client", virtual_port, size), + Err(e) => { + error!("[{}] Failed to send {} bytes to local client: {:?}", virtual_port, size, e); break; - } else { - continue; } } - Err(e) => { - error!( - "[{}] Failed to write to client TCP socket: {:?}", - virtual_port, e - ); - } - }, - None => { - if abort.load(Ordering::Relaxed) { - break; - } else { - continue; - } - }, + } + _ => {} } } } } - trace!("[{}] TCP socket handler task terminated", virtual_port); - abort.store(true, Ordering::Relaxed); + // Notify other endpoints that this task has closed and no more data is to be sent to the local client + endpoint.send(Event::ClientConnectionDropped(virtual_port)); + Ok(()) } @@ -230,19 +171,19 @@ impl TcpPortPool { } /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). - pub async fn next(&self) -> anyhow::Result { + pub async fn next(&self) -> anyhow::Result { let mut inner = self.inner.write().await; let port = inner .queue .pop_front() .with_context(|| "TCP virtual port pool is exhausted")?; - Ok(port) + Ok(VirtualPort::new(port, PortProtocol::Tcp)) } /// Releases a port back into the pool. - pub async fn release(&self, port: u16) { + pub async fn release(&self, port: VirtualPort) { let mut inner = self.inner.write().await; - inner.queue.push_back(port); + inner.queue.push_back(port.num()); } } diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index bedcdf7..34fb26d 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Instant; +use crate::events::{Bus, Event}; use anyhow::Context; use priority_queue::double_priority_queue::DoublePriorityQueue; use priority_queue::priority_queue::PriorityQueue; @@ -30,61 +31,24 @@ const UDP_TIMEOUT_SECONDS: u64 = 60; /// TODO: Make this configurable by the CLI const PORTS_PER_IP: usize = 100; +/// Starts the server that listens on UDP datagrams. pub async fn udp_proxy_server( port_forward: PortForwardConfig, port_pool: UdpPortPool, - wg: Arc, + bus: Bus, ) -> anyhow::Result<()> { - // Abort signal - let abort = Arc::new(AtomicBool::new(false)); - - // data_to_real_client_(tx/rx): This task reads the data from this mpsc channel to send back - // to the real client. - let (data_to_real_client_tx, mut data_to_real_client_rx) = - tokio::sync::mpsc::channel::<(VirtualPort, Vec)>(1_000); - - // data_to_real_server_(tx/rx): This task sends the data received from the real client to the - // virtual interface (virtual server socket). - let (data_to_virtual_server_tx, data_to_virtual_server_rx) = - tokio::sync::mpsc::channel::<(VirtualPort, Vec)>(1_000); - - { - // Spawn virtual interface - // Note: contrary to TCP, there is only one UDP virtual interface - let virtual_interface = UdpVirtualInterface::new( - port_forward, - wg, - data_to_real_client_tx, - data_to_virtual_server_rx, - ); - let abort = abort.clone(); - tokio::spawn(async move { - virtual_interface.poll_loop().await.unwrap_or_else(|e| { - error!("Virtual interface poll loop failed unexpectedly: {}", e); - abort.store(true, Ordering::Relaxed); - }); - }); - } - + let mut endpoint = bus.new_endpoint(); let socket = UdpSocket::bind(port_forward.source) .await .with_context(|| "Failed to bind on UDP proxy address")?; let mut buffer = [0u8; MAX_PACKET]; loop { - if abort.load(Ordering::Relaxed) { - break; - } tokio::select! { to_send_result = next_udp_datagram(&socket, &mut buffer, port_pool.clone()) => { match to_send_result { Ok(Some((port, data))) => { - data_to_virtual_server_tx.send((port, data)).await.unwrap_or_else(|e| { - error!( - "Failed to dispatch data to UDP virtual interface: {:?}", - e - ); - }); + endpoint.send(Event::LocalData(port, data)); } Ok(None) => { continue; @@ -98,9 +62,9 @@ pub async fn udp_proxy_server( } } } - data_recv_result = data_to_real_client_rx.recv() => { - if let Some((port, data)) = data_recv_result { - if let Some(peer_addr) = port_pool.get_peer_addr(port.0).await { + event = endpoint.recv() => { + if let Event::RemoteData(port, data) = event { + if let Some(peer_addr) = port_pool.get_peer_addr(port).await { if let Err(e) = socket.send_to(&data, peer_addr).await { error!( "[{}] Failed to send UDP datagram to real client ({}): {:?}", @@ -109,7 +73,7 @@ pub async fn udp_proxy_server( e, ); } - port_pool.update_last_transmit(port.0).await; + port_pool.update_last_transmit(port).await; } } } @@ -141,14 +105,13 @@ async fn next_udp_datagram( return Ok(None); } }; - let port = VirtualPort(port, PortProtocol::Udp); debug!( "[{}] Received datagram of {} bytes from {}", port, size, peer_addr ); - port_pool.update_last_transmit(port.0).await; + port_pool.update_last_transmit(port).await; let data = buffer[..size].to_vec(); Ok(Some((port, data))) @@ -181,14 +144,14 @@ impl UdpPortPool { } /// Requests a free port from the pool. An error is returned if none is available (exhausted max capacity). - pub async fn next(&self, peer_addr: SocketAddr) -> anyhow::Result { + pub async fn next(&self, peer_addr: SocketAddr) -> anyhow::Result { // A port found to be reused. This is outside of the block because the read lock cannot be upgraded to a write lock. let mut port_reuse: Option = None; { let inner = self.inner.read().await; if let Some(port) = inner.port_by_peer_addr.get(&peer_addr) { - return Ok(*port); + return Ok(VirtualPort::new(*port, PortProtocol::Udp)); } // Count how many ports are being used by the peer IP @@ -240,26 +203,26 @@ impl UdpPortPool { inner.port_by_peer_addr.insert(peer_addr, port); inner.peer_addr_by_port.insert(port, peer_addr); - Ok(port) + Ok(VirtualPort::new(port, PortProtocol::Udp)) } /// Notify that the given virtual port has received or transmitted a UDP datagram. - pub async fn update_last_transmit(&self, port: u16) { + pub async fn update_last_transmit(&self, port: VirtualPort) { let mut inner = self.inner.write().await; - if let Some(peer) = inner.peer_addr_by_port.get(&port).copied() { + if let Some(peer) = inner.peer_addr_by_port.get(&port.num()).copied() { let mut pq: &mut DoublePriorityQueue = inner .peer_port_usage .entry(peer.ip()) .or_insert_with(Default::default); - pq.push(port, Instant::now()); + pq.push(port.num(), Instant::now()); } let mut pq: &mut DoublePriorityQueue = &mut inner.port_usage; - pq.push(port, Instant::now()); + pq.push(port.num(), Instant::now()); } - pub async fn get_peer_addr(&self, port: u16) -> Option { + pub async fn get_peer_addr(&self, port: VirtualPort) -> Option { let inner = self.inner.read().await; - inner.peer_addr_by_port.get(&port).copied() + inner.peer_addr_by_port.get(&port.num()).copied() } } diff --git a/src/virtual_device.rs b/src/virtual_device.rs index d06d7fa..e0e7e4d 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -1,45 +1,51 @@ -use crate::virtual_iface::VirtualPort; -use crate::wg::{WireGuardTunnel, DISPATCH_CAPACITY}; -use anyhow::Context; +use crate::config::PortProtocol; +use crate::events::{BusSender, Event}; +use crate::Bus; use smoltcp::phy::{Device, DeviceCapabilities, Medium}; use smoltcp::time::Instant; -use std::sync::Arc; +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; -/// A virtual device that processes IP packets. IP packets received from the WireGuard endpoint -/// are made available to this device using a channel receiver. IP packets sent from this device -/// are asynchronously sent out to the WireGuard tunnel. +/// A virtual device that processes IP packets through smoltcp and WireGuard. pub struct VirtualIpDevice { - /// Tunnel to send IP packets to. - wg: Arc, + /// Max transmission unit (bytes) + max_transmission_unit: usize, /// Channel receiver for received IP packets. - ip_dispatch_rx: tokio::sync::mpsc::Receiver>, + bus_sender: BusSender, + /// Local queue for packets received from the bus that need to go through the smoltcp interface. + process_queue: Arc>>>, } impl VirtualIpDevice { /// Initializes a new virtual IP device. - pub fn new( - wg: Arc, - ip_dispatch_rx: tokio::sync::mpsc::Receiver>, - ) -> Self { - Self { wg, ip_dispatch_rx } - } + pub fn new(protocol: PortProtocol, bus: Bus, max_transmission_unit: usize) -> Self { + let mut bus_endpoint = bus.new_endpoint(); + let bus_sender = bus_endpoint.sender(); + let process_queue = Arc::new(Mutex::new(VecDeque::new())); - /// Registers a virtual IP device for a single virtual client. - pub fn new_direct(virtual_port: VirtualPort, wg: Arc) -> anyhow::Result { - let (ip_dispatch_tx, ip_dispatch_rx) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); + { + let process_queue = process_queue.clone(); + tokio::spawn(async move { + loop { + match bus_endpoint.recv().await { + Event::InboundInternetPacket(ip_proto, data) if ip_proto == protocol => { + let mut queue = process_queue + .lock() + .expect("Failed to acquire process queue lock"); + queue.push_back(data); + bus_endpoint.send(Event::VirtualDeviceFed(ip_proto)); + } + _ => {} + } + } + }); + } - wg.register_virtual_interface(virtual_port, ip_dispatch_tx) - .with_context(|| "Failed to register IP dispatch for virtual interface")?; - - Ok(Self { wg, ip_dispatch_rx }) - } - - pub async fn new_sink(wg: Arc) -> anyhow::Result { - let ip_dispatch_rx = wg - .register_sink_interface() - .await - .with_context(|| "Failed to register IP dispatch for sink virtual interface")?; - Ok(Self { wg, ip_dispatch_rx }) + Self { + bus_sender, + process_queue, + max_transmission_unit, + } } } @@ -48,27 +54,34 @@ impl<'a> Device<'a> for VirtualIpDevice { type TxToken = TxToken; fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { - match self.ip_dispatch_rx.try_recv() { - Ok(buffer) => Some(( + let next = { + let mut queue = self + .process_queue + .lock() + .expect("Failed to acquire process queue lock"); + queue.pop_front() + }; + match next { + Some(buffer) => Some(( Self::RxToken { buffer }, Self::TxToken { - wg: self.wg.clone(), + sender: self.bus_sender.clone(), }, )), - Err(_) => None, + None => None, } } fn transmit(&'a mut self) -> Option { Some(TxToken { - wg: self.wg.clone(), + sender: self.bus_sender.clone(), }) } fn capabilities(&self) -> DeviceCapabilities { let mut cap = DeviceCapabilities::default(); cap.medium = Medium::Ip; - cap.max_transmission_unit = self.wg.max_transmission_unit; + cap.max_transmission_unit = self.max_transmission_unit; cap } } @@ -89,7 +102,7 @@ impl smoltcp::phy::RxToken for RxToken { #[doc(hidden)] pub struct TxToken { - wg: Arc, + sender: BusSender, } impl smoltcp::phy::TxToken for TxToken { @@ -100,14 +113,7 @@ impl smoltcp::phy::TxToken for TxToken { let mut buffer = Vec::new(); buffer.resize(len, 0); let result = f(&mut buffer); - tokio::spawn(async move { - match self.wg.send_ip_packet(&buffer).await { - Ok(_) => {} - Err(e) => { - error!("Failed to send IP packet to WireGuard endpoint: {:?}", e); - } - } - }); + self.sender.send(Event::OutboundInternetPacket(buffer)); result } } diff --git a/src/virtual_iface/mod.rs b/src/virtual_iface/mod.rs index f3796bd..ea3cd74 100644 --- a/src/virtual_iface/mod.rs +++ b/src/virtual_iface/mod.rs @@ -14,10 +14,51 @@ pub trait VirtualInterfacePoll { /// Virtual port. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct VirtualPort(pub u16, pub PortProtocol); +pub struct VirtualPort(u16, PortProtocol); + +impl VirtualPort { + /// Create a new `VirtualPort` instance, with the given port number and associated protocol. + pub fn new(port: u16, proto: PortProtocol) -> Self { + VirtualPort(port, proto) + } + + /// The port number + pub fn num(&self) -> u16 { + self.0 + } + + /// The protocol of this port. + pub fn proto(&self) -> PortProtocol { + self.1 + } +} + +impl From for u16 { + fn from(port: VirtualPort) -> Self { + port.num() + } +} + +impl From<&VirtualPort> for u16 { + fn from(port: &VirtualPort) -> Self { + port.num() + } +} + +impl From for PortProtocol { + fn from(port: VirtualPort) -> Self { + port.proto() + } +} + +impl From<&VirtualPort> for PortProtocol { + fn from(port: &VirtualPort) -> Self { + port.proto() + } +} impl Display for VirtualPort { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}:{}]", self.0, self.1) + write!(f, "[{}:{}]", self.num(), self.proto()) } } diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 340242a..6c283f1 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -1,284 +1,253 @@ use crate::config::{PortForwardConfig, PortProtocol}; +use crate::events::Event; use crate::virtual_device::VirtualIpDevice; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; -use crate::wg::WireGuardTunnel; +use crate::Bus; use anyhow::Context; use async_trait::async_trait; -use smoltcp::iface::InterfaceBuilder; +use smoltcp::iface::{InterfaceBuilder, SocketHandle}; use smoltcp::socket::{TcpSocket, TcpSocketBuffer, TcpState}; use smoltcp::wire::{IpAddress, IpCidr}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::net::IpAddr; use std::time::Duration; const MAX_PACKET: usize = 65536; /// A virtual interface for proxying Layer 7 data to Layer 3 packets, and vice-versa. pub struct TcpVirtualInterface { - /// The virtual port assigned to the virtual client, used to - /// route Layer 4 segments/datagrams to and from the WireGuard tunnel. - virtual_port: u16, - /// The overall port-forward configuration: used for the destination address (on which - /// the virtual server listens) and the protocol in use. - port_forward: PortForwardConfig, - /// The WireGuard tunnel to send IP packets to. - wg: Arc, - /// Abort signal to shutdown the virtual interface and its parent task. - abort: Arc, - /// Channel sender for pushing Layer 7 data back to the real client. - data_to_real_client_tx: tokio::sync::mpsc::Sender>, - /// Channel receiver for processing Layer 7 data through the virtual interface. - data_to_virtual_server_rx: tokio::sync::mpsc::Receiver>, - /// One-shot sender to notify the parent task that the virtual client is ready to send Layer 7 data. - virtual_client_ready_tx: tokio::sync::oneshot::Sender<()>, + source_peer_ip: IpAddr, + port_forwards: Vec, + device: VirtualIpDevice, + bus: Bus, } impl TcpVirtualInterface { /// Initialize the parameters for a new virtual interface. /// Use the `poll_loop()` future to start the virtual interface poll loop. pub fn new( - virtual_port: u16, - port_forward: PortForwardConfig, - wg: Arc, - abort: Arc, - data_to_real_client_tx: tokio::sync::mpsc::Sender>, - data_to_virtual_server_rx: tokio::sync::mpsc::Receiver>, - virtual_client_ready_tx: tokio::sync::oneshot::Sender<()>, + port_forwards: Vec, + bus: Bus, + device: VirtualIpDevice, + source_peer_ip: IpAddr, ) -> Self { Self { - virtual_port, - port_forward, - wg, - abort, - data_to_real_client_tx, - data_to_virtual_server_rx, - virtual_client_ready_tx, + port_forwards: port_forwards + .into_iter() + .filter(|f| matches!(f.protocol, PortProtocol::Tcp)) + .collect(), + device, + source_peer_ip, + bus, } } + + fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { + static mut TCP_SERVER_RX_DATA: [u8; 0] = []; + static mut TCP_SERVER_TX_DATA: [u8; 0] = []; + + let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); + let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + + socket + .listen(( + IpAddress::from(port_forward.destination.ip()), + port_forward.destination.port(), + )) + .with_context(|| "Virtual server socket failed to listen")?; + + Ok(socket) + } + + fn new_client_socket() -> anyhow::Result> { + let rx_data = vec![0u8; MAX_PACKET]; + let tx_data = vec![0u8; MAX_PACKET]; + let tcp_rx_buffer = TcpSocketBuffer::new(rx_data); + let tcp_tx_buffer = TcpSocketBuffer::new(tx_data); + let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + Ok(socket) + } } #[async_trait] impl VirtualInterfacePoll for TcpVirtualInterface { async fn poll_loop(self) -> anyhow::Result<()> { - let mut virtual_client_ready_tx = Some(self.virtual_client_ready_tx); - let mut data_to_virtual_server_rx = self.data_to_virtual_server_rx; - let source_peer_ip = self.wg.source_peer_ip; + // Create CIDR block for source peer IP + each port forward IP + let addresses: Vec = { + let mut addresses = HashSet::new(); + addresses.insert(IpAddress::from(self.source_peer_ip)); + for config in self.port_forwards.iter() { + addresses.insert(IpAddress::from(config.destination.ip())); + } + addresses + .into_iter() + .map(|addr| IpCidr::new(addr, 32)) + .collect() + }; - // Create a device and interface to simulate IP packets - // In essence: - // * TCP packets received from the 'real' client are 'sent' to the 'virtual server' via the 'virtual client' - // * Those TCP packets generate IP packets, which are captured from the interface and sent to the WireGuardTunnel - // * IP packets received by the WireGuardTunnel (from the endpoint) are fed into this 'virtual interface' - // * The interface processes those IP packets and routes them to the 'virtual client' (the rest is discarded) - // * The TCP data read by the 'virtual client' is sent to the 'real' TCP client - - // Consumer for IP packets to send through the virtual interface - // Initialize the interface - let device = - VirtualIpDevice::new_direct(VirtualPort(self.virtual_port, PortProtocol::Tcp), self.wg) - .with_context(|| "Failed to initialize TCP VirtualIpDevice")?; - - // there are always 2 sockets: 1 virtual client and 1 virtual server. - let mut sockets: [_; 2] = Default::default(); - let mut virtual_interface = InterfaceBuilder::new(device, &mut sockets[..]) - .ip_addrs([ - // Interface handles IP packets for the sender and recipient - IpCidr::new(IpAddress::from(source_peer_ip), 32), - IpCidr::new(IpAddress::from(self.port_forward.destination.ip()), 32), - ]) + let mut iface = InterfaceBuilder::new(self.device, vec![]) + .ip_addrs(addresses) .finalize(); - // Server socket: this is a placeholder for the interface to route new connections to. - let server_socket: anyhow::Result = { - static mut TCP_SERVER_RX_DATA: [u8; 0] = []; - static mut TCP_SERVER_TX_DATA: [u8; 0] = []; - let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); - let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); - let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + // Create virtual server for each port forward + for port_forward in self.port_forwards.iter() { + let server_socket = TcpVirtualInterface::new_server_socket(*port_forward)?; + iface.add_socket(server_socket); + } - socket - .listen(( - IpAddress::from(self.port_forward.destination.ip()), - self.port_forward.destination.port(), - )) - .with_context(|| "Virtual server socket failed to listen")?; + // The next time to poll the interface. Can be None for instant poll. + let mut next_poll: Option = None; - Ok(socket) - }; + // Bus endpoint to read events + let mut endpoint = self.bus.new_endpoint(); - let client_socket: anyhow::Result = { - let rx_data = vec![0u8; MAX_PACKET]; - let tx_data = vec![0u8; MAX_PACKET]; - let tcp_rx_buffer = TcpSocketBuffer::new(rx_data); - let tcp_tx_buffer = TcpSocketBuffer::new(tx_data); - let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - Ok(socket) - }; + let mut port_client_handle_map: HashMap = HashMap::new(); - let _server_handle = virtual_interface.add_socket(server_socket?); - let client_handle = virtual_interface.add_socket(client_socket?); - - // Any data that wasn't sent because it was over the sending buffer limit - let mut tx_extra = Vec::new(); - - // Counts the connection attempts by the virtual client - let mut connection_attempts = 0; - // Whether the client has successfully connected before. Prevents the case of connecting again. - let mut has_connected = false; + // Data packets to send from a virtual client + let mut send_queue: HashMap>> = HashMap::new(); loop { - let loop_start = smoltcp::time::Instant::now(); + tokio::select! { + _ = match (next_poll, port_client_handle_map.len()) { + (None, 0) => tokio::time::sleep(Duration::MAX), + (None, _) => tokio::time::sleep(Duration::ZERO), + (Some(until), _) => tokio::time::sleep_until(until), + } => { + let loop_start = smoltcp::time::Instant::now(); - // Shutdown occurs when the real client closes the connection, - // or if the client was in a CLOSE-WAIT state (after a server FIN) and had no data to send anymore. - // One last poll-loop iteration is executed so that the RST segment can be dispatched. - let shutdown = self.abort.load(Ordering::Relaxed); - - if shutdown { - // Shutdown: sends a RST packet. - trace!("[{}] Shutting down virtual interface", self.virtual_port); - let client_socket = virtual_interface.get_socket::(client_handle); - client_socket.abort(); - } - - match virtual_interface.poll(loop_start) { - Ok(processed) if processed => { - trace!( - "[{}] Virtual interface polled some packets to be processed", - self.virtual_port - ); - } - Err(e) => { - error!( - "[{}] Virtual interface poll error: {:?}", - self.virtual_port, e - ); - } - _ => {} - } - - { - let (client_socket, context) = - virtual_interface.get_socket_and_context::(client_handle); - - if !shutdown && client_socket.state() == TcpState::Closed && !has_connected { - // Not shutting down, but the client socket is closed, and the client never successfully connected. - if connection_attempts < 10 { - // Try to connect - client_socket - .connect( - context, - ( - IpAddress::from(self.port_forward.destination.ip()), - self.port_forward.destination.port(), - ), - (IpAddress::from(source_peer_ip), self.virtual_port), - ) - .with_context(|| "Virtual server socket failed to listen")?; - if connection_attempts > 0 { - debug!( - "[{}] Virtual client retrying connection in 500ms", - self.virtual_port - ); - // Not our first connection attempt, wait a little bit. - tokio::time::sleep(Duration::from_millis(500)).await; + match iface.poll(loop_start) { + Ok(processed) if processed => { + trace!("TCP virtual interface polled some packets to be processed"); } - } else { - // Too many connection attempts - self.abort.store(true, Ordering::Relaxed); - } - connection_attempts += 1; - continue; - } - - if client_socket.state() == TcpState::Established { - // Prevent reconnection if the server later closes. - has_connected = true; - } - - if client_socket.can_recv() { - match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { - Ok(data) => { - trace!( - "[{}] Virtual client received {} bytes of data", - self.virtual_port, - data.len() - ); - // Send it to the real client - if let Err(e) = self.data_to_real_client_tx.send(data).await { - error!("[{}] Failed to dispatch data from virtual client to real client: {:?}", self.virtual_port, e); - } - } - Err(e) => { - error!( - "[{}] Failed to read from virtual client socket: {:?}", - self.virtual_port, e - ); - } - } - } - if client_socket.can_send() { - if let Some(virtual_client_ready_tx) = virtual_client_ready_tx.take() { - virtual_client_ready_tx - .send(()) - .expect("Failed to notify real client that virtual client is ready"); + Err(e) => error!("TCP virtual interface poll error: {:?}", e), + _ => {} } - let mut to_transfer = None; - - if tx_extra.is_empty() { - // The payload segment from the previous loop is complete, - // we can now read the next payload in the queue. - if let Ok(data) = data_to_virtual_server_rx.try_recv() { - to_transfer = Some(data); - } else if client_socket.state() == TcpState::CloseWait { - // No data to be sent in this loop. If the client state is CLOSE-WAIT (because of a server FIN), - // the interface is shutdown. - trace!("[{}] Shutting down virtual interface because client sent no more data, and server sent FIN (CLOSE-WAIT)", self.virtual_port); - self.abort.store(true, Ordering::Relaxed); - continue; - } - } - - let to_transfer_slice = to_transfer.as_ref().unwrap_or(&tx_extra).as_slice(); - if !to_transfer_slice.is_empty() { - let total = to_transfer_slice.len(); - match client_socket.send_slice(to_transfer_slice) { - Ok(sent) => { - trace!( - "[{}] Sent {}/{} bytes via virtual client socket", - self.virtual_port, - sent, - total, - ); - tx_extra = Vec::from(&to_transfer_slice[sent..total]); - } - Err(e) => { - error!( - "[{}] Failed to send slice via virtual client socket: {:?}", - self.virtual_port, e - ); + // Find client socket send data to + for (virtual_port, client_handle) in port_client_handle_map.iter() { + let client_socket = iface.get_socket::(*client_handle); + if client_socket.can_send() { + if let Some(send_queue) = send_queue.get_mut(virtual_port) { + let to_transfer = send_queue.pop_front(); + if let Some(to_transfer_slice) = to_transfer.as_deref() { + let total = to_transfer_slice.len(); + match client_socket.send_slice(to_transfer_slice) { + Ok(sent) => { + if sent < total { + // Sometimes only a subset is sent, so the rest needs to be sent on the next poll + let tx_extra = Vec::from(&to_transfer_slice[sent..total]); + send_queue.push_front(tx_extra); + } + } + Err(e) => { + error!( + "Failed to send slice via virtual client socket: {:?}", e + ); + } + } + } else if client_socket.state() == TcpState::CloseWait { + client_socket.close(); + } + break; } } } - } - } - if shutdown { - break; - } + // Find client socket recv data from + for (virtual_port, client_handle) in port_client_handle_map.iter() { + let client_socket = iface.get_socket::(*client_handle); + if client_socket.can_recv() { + match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { + Ok(data) => { + if !data.is_empty() { + endpoint.send(Event::RemoteData(*virtual_port, data)); + break; + } else { + continue; + } + } + Err(e) => { + error!( + "Failed to read from virtual client socket: {:?}", e + ); + } + } + } + } - match virtual_interface.poll_delay(loop_start) { - Some(smoltcp::time::Duration::ZERO) => { - continue; + // Find closed sockets + port_client_handle_map.retain(|virtual_port, client_handle| { + let client_socket = iface.get_socket::(*client_handle); + if client_socket.state() == TcpState::Closed { + endpoint.send(Event::ClientConnectionDropped(*virtual_port)); + send_queue.remove(virtual_port); + false + } else { + // Not closed, retain + true + } + }); + + // The virtual interface determines the next time to poll (this is to reduce unnecessary polls) + next_poll = match iface.poll_delay(loop_start) { + Some(smoltcp::time::Duration::ZERO) => None, + Some(delay) => { + trace!("TCP Virtual interface delayed next poll by {}", delay); + Some(tokio::time::Instant::now() + Duration::from_millis(delay.total_millis())) + }, + None => None, + }; } - _ => { - tokio::time::sleep(Duration::from_millis(1)).await; + event = endpoint.recv() => { + match event { + Event::ClientConnectionInitiated(port_forward, virtual_port) => { + let client_socket = TcpVirtualInterface::new_client_socket()?; + let client_handle = iface.add_socket(client_socket); + + // Add handle to map + port_client_handle_map.insert(virtual_port, client_handle); + send_queue.insert(virtual_port, VecDeque::new()); + + let (client_socket, context) = iface.get_socket_and_context::(client_handle); + + client_socket + .connect( + context, + ( + IpAddress::from(port_forward.destination.ip()), + port_forward.destination.port(), + ), + (IpAddress::from(self.source_peer_ip), virtual_port.num()), + ) + .with_context(|| "Virtual server socket failed to listen")?; + + next_poll = None; + } + Event::ClientConnectionDropped(virtual_port) => { + if let Some(client_handle) = port_client_handle_map.get(&virtual_port) { + let client_handle = *client_handle; + port_client_handle_map.remove(&virtual_port); + send_queue.remove(&virtual_port); + + let client_socket = iface.get_socket::(client_handle); + client_socket.close(); + next_poll = None; + } + } + Event::LocalData(virtual_port, data) if send_queue.contains_key(&virtual_port) => { + if let Some(send_queue) = send_queue.get_mut(&virtual_port) { + send_queue.push_back(data); + next_poll = None; + } + } + Event::VirtualDeviceFed(protocol) if protocol == PortProtocol::Tcp => { + next_poll = None; + } + _ => {} + } } } } - trace!("[{}] Virtual interface task terminated", self.virtual_port); - self.abort.store(true, Ordering::Relaxed); - Ok(()) } } diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 1fdbb63..022a2c2 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -1,39 +1,39 @@ -use anyhow::Context; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; +#![allow(dead_code)] +use std::net::IpAddr; +use crate::{Bus, PortProtocol}; use async_trait::async_trait; -use smoltcp::iface::{InterfaceBuilder, SocketHandle}; -use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; -use smoltcp::wire::{IpAddress, IpCidr}; use crate::config::PortForwardConfig; use crate::virtual_device::VirtualIpDevice; -use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; -use crate::wg::{WireGuardTunnel, DISPATCH_CAPACITY}; +use crate::virtual_iface::VirtualInterfacePoll; const MAX_PACKET: usize = 65536; pub struct UdpVirtualInterface { - port_forward: PortForwardConfig, - wg: Arc, - data_to_real_client_tx: tokio::sync::mpsc::Sender<(VirtualPort, Vec)>, - data_to_virtual_server_rx: tokio::sync::mpsc::Receiver<(VirtualPort, Vec)>, + source_peer_ip: IpAddr, + port_forwards: Vec, + device: VirtualIpDevice, + bus: Bus, } impl UdpVirtualInterface { + /// Initialize the parameters for a new virtual interface. + /// Use the `poll_loop()` future to start the virtual interface poll loop. pub fn new( - port_forward: PortForwardConfig, - wg: Arc, - data_to_real_client_tx: tokio::sync::mpsc::Sender<(VirtualPort, Vec)>, - data_to_virtual_server_rx: tokio::sync::mpsc::Receiver<(VirtualPort, Vec)>, + port_forwards: Vec, + bus: Bus, + device: VirtualIpDevice, + source_peer_ip: IpAddr, ) -> Self { Self { - port_forward, - wg, - data_to_real_client_tx, - data_to_virtual_server_rx, + port_forwards: port_forwards + .into_iter() + .filter(|f| matches!(f.protocol, PortProtocol::Udp)) + .collect(), + device, + source_peer_ip, + bus, } } } @@ -41,160 +41,9 @@ impl UdpVirtualInterface { #[async_trait] impl VirtualInterfacePoll for UdpVirtualInterface { async fn poll_loop(self) -> anyhow::Result<()> { - // Data receiver to dispatch using virtual client sockets - let mut data_to_virtual_server_rx = self.data_to_virtual_server_rx; - - // The IP to bind client sockets to - let source_peer_ip = self.wg.source_peer_ip; - - // The IP/port to bind the server socket to - let destination = self.port_forward.destination; - - // Initialize a channel for IP packets. - // The "base transmitted" is cloned so that each virtual port can register a sender in the tunnel. - // The receiver is given to the device so that the Virtual Interface can process incoming IP packets from the tunnel. - let (base_ip_dispatch_tx, ip_dispatch_rx) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); - - let device = VirtualIpDevice::new(self.wg.clone(), ip_dispatch_rx); - let mut virtual_interface = InterfaceBuilder::new(device, vec![]) - .ip_addrs([ - // Interface handles IP packets for the sender and recipient - IpCidr::new(source_peer_ip.into(), 32), - IpCidr::new(destination.ip().into(), 32), - ]) - .finalize(); - - // Server socket: this is a placeholder for the interface. - let server_socket: anyhow::Result = { - static mut UDP_SERVER_RX_META: [UdpPacketMetadata; 0] = []; - static mut UDP_SERVER_RX_DATA: [u8; 0] = []; - static mut UDP_SERVER_TX_META: [UdpPacketMetadata; 0] = []; - static mut UDP_SERVER_TX_DATA: [u8; 0] = []; - let udp_rx_buffer = - UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe { - &mut UDP_SERVER_RX_DATA[..] - }); - let udp_tx_buffer = - UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe { - &mut UDP_SERVER_TX_DATA[..] - }); - let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); - - socket - .bind((IpAddress::from(destination.ip()), destination.port())) - .with_context(|| "UDP virtual server socket failed to listen")?; - - Ok(socket) - }; - - let _server_handle = virtual_interface.add_socket(server_socket?); - - // A map of virtual port to client socket. - let mut client_sockets: HashMap = HashMap::new(); - - // The next instant required to poll the virtual interface - // None means "immediate poll required". - let mut next_poll: Option = None; - - loop { - let wg = self.wg.clone(); - tokio::select! { - // Wait the recommended amount of time by smoltcp, and poll again. - _ = match next_poll { - None => tokio::time::sleep(Duration::ZERO), - Some(until) => tokio::time::sleep_until(until) - } => { - let loop_start = smoltcp::time::Instant::now(); - - match virtual_interface.poll(loop_start) { - Ok(processed) if processed => { - trace!("UDP virtual interface polled some packets to be processed"); - } - Err(e) => error!("UDP virtual interface poll error: {:?}", e), - _ => {} - } - - // Loop through each client socket and check if there is any data to send back - // to the real client. - for (virtual_port, client_socket_handle) in client_sockets.iter() { - let client_socket = virtual_interface.get_socket::(*client_socket_handle); - match client_socket.recv() { - Ok((data, _peer)) => { - // Send the data back to the real client using MPSC channel - self.data_to_real_client_tx - .send((*virtual_port, data.to_vec())) - .await - .unwrap_or_else(|e| { - error!( - "[{}] Failed to dispatch data from virtual client to real client: {:?}", - virtual_port, e - ); - }); - } - Err(smoltcp::Error::Exhausted) => {} - Err(e) => { - error!( - "[{}] Failed to read from virtual client socket: {:?}", - virtual_port, e - ); - } - } - } - - next_poll = match virtual_interface.poll_delay(loop_start) { - Some(smoltcp::time::Duration::ZERO) => None, - Some(delay) => Some(tokio::time::Instant::now() + Duration::from_millis(delay.millis())), - None => None, - } - } - // Wait for data to be received from the real client - data_recv_result = data_to_virtual_server_rx.recv() => { - if let Some((client_port, data)) = data_recv_result { - // Register the socket in WireGuard Tunnel (overrides any previous registration as well) - wg.register_virtual_interface(client_port, base_ip_dispatch_tx.clone()) - .unwrap_or_else(|e| { - error!( - "[{}] Failed to register UDP socket in WireGuard tunnel: {:?}", - client_port, e - ); - }); - - let client_socket_handle = client_sockets.entry(client_port).or_insert_with(|| { - let rx_meta = vec![UdpPacketMetadata::EMPTY; 10]; - let tx_meta = vec![UdpPacketMetadata::EMPTY; 10]; - let rx_data = vec![0u8; MAX_PACKET]; - let tx_data = vec![0u8; MAX_PACKET]; - let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data); - let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data); - let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); - - socket - .bind((IpAddress::from(wg.source_peer_ip), client_port.0)) - .unwrap_or_else(|e| { - error!( - "[{}] UDP virtual client socket failed to bind: {:?}", - client_port, e - ); - }); - - virtual_interface.add_socket(socket) - }); - - let client_socket = virtual_interface.get_socket::(*client_socket_handle); - client_socket - .send_slice( - &data, - (IpAddress::from(destination.ip()), destination.port()).into(), - ) - .unwrap_or_else(|e| { - error!( - "[{}] Failed to send data to virtual server: {:?}", - client_port, e - ); - }); - } - } - } - } + // TODO: Create smoltcp virtual device and interface + // TODO: Create smoltcp virtual servers for `port_forwards` + // TODO: listen on events + futures::future::pending().await } } diff --git a/src/wg.rs b/src/wg.rs index a7f56e5..60767e3 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -1,15 +1,15 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::time::Duration; +use crate::Bus; use anyhow::Context; use boringtun::noise::{Tunn, TunnResult}; use log::Level; -use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket}; +use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet}; use tokio::net::UdpSocket; -use tokio::sync::RwLock; use crate::config::{Config, PortProtocol}; -use crate::virtual_iface::VirtualPort; +use crate::events::Event; /// The capacity of the channel for received IP packets. pub const DISPATCH_CAPACITY: usize = 1_000; @@ -26,17 +26,13 @@ pub struct WireGuardTunnel { udp: UdpSocket, /// The address of the public WireGuard endpoint (UDP). pub(crate) endpoint: SocketAddr, - /// Maps virtual ports to the corresponding IP packet dispatcher. - virtual_port_ip_tx: dashmap::DashMap>>, - /// IP packet dispatcher for unroutable packets. `None` if not initialized. - sink_ip_tx: RwLock>>>, - /// The max transmission unit for WireGuard. - pub(crate) max_transmission_unit: usize, + /// Event bus + bus: Bus, } impl WireGuardTunnel { /// Initialize a new WireGuard tunnel. - pub async fn new(config: &Config) -> anyhow::Result { + pub async fn new(config: &Config, bus: Bus) -> anyhow::Result { let source_peer_ip = config.source_peer_ip; let peer = Self::create_tunnel(config)?; let endpoint = config.endpoint_addr; @@ -46,16 +42,13 @@ impl WireGuardTunnel { }) .await .with_context(|| "Failed to create UDP socket for WireGuard connection")?; - let virtual_port_ip_tx = Default::default(); Ok(Self { source_peer_ip, peer, udp, endpoint, - virtual_port_ip_tx, - sink_ip_tx: RwLock::new(None), - max_transmission_unit: config.max_transmission_unit, + bus, }) } @@ -90,31 +83,20 @@ impl WireGuardTunnel { Ok(()) } - /// Register a virtual interface (using its assigned virtual port) with the given IP packet `Sender`. - pub fn register_virtual_interface( - &self, - virtual_port: VirtualPort, - sender: tokio::sync::mpsc::Sender>, - ) -> anyhow::Result<()> { - self.virtual_port_ip_tx.insert(virtual_port, sender); - Ok(()) - } + pub async fn produce_task(&self) -> ! { + trace!("Starting WireGuard production task"); + let mut endpoint = self.bus.new_endpoint(); - /// Register a virtual interface (using its assigned virtual port) with the given IP packet `Sender`. - pub async fn register_sink_interface( - &self, - ) -> anyhow::Result>> { - let (sender, receiver) = tokio::sync::mpsc::channel(DISPATCH_CAPACITY); - - let mut sink_ip_tx = self.sink_ip_tx.write().await; - *sink_ip_tx = Some(sender); - - Ok(receiver) - } - - /// Releases the virtual interface from IP dispatch. - pub fn release_virtual_interface(&self, virtual_port: VirtualPort) { - self.virtual_port_ip_tx.remove(&virtual_port); + loop { + if let Event::OutboundInternetPacket(data) = endpoint.recv().await { + match self.send_ip_packet(&data).await { + Ok(_) => {} + Err(e) => { + error!("{:?}", e); + } + } + } + } } /// WireGuard Routine task. Handles Handshake, keep-alive, etc. @@ -160,6 +142,7 @@ impl WireGuardTunnel { /// decapsulates them, and dispatches newly received IP packets. pub async fn consume_task(&self) -> ! { trace!("Starting WireGuard consumption task"); + let endpoint = self.bus.new_endpoint(); loop { let mut recv_buf = [0u8; MAX_PACKET]; @@ -212,38 +195,8 @@ impl WireGuardTunnel { // For debugging purposes: parse packet trace_ip_packet("Received IP packet", packet); - match self.route_ip_packet(packet) { - RouteResult::Dispatch(port) => { - let sender = self.virtual_port_ip_tx.get(&port); - if let Some(sender_guard) = sender { - let sender = sender_guard.value(); - match sender.send(packet.to_vec()).await { - Ok(_) => { - trace!( - "Dispatched received IP packet to virtual port {}", - port - ); - } - Err(e) => { - error!( - "Failed to dispatch received IP packet to virtual port {}: {}", - port, e - ); - } - } - } else { - warn!("[{}] Race condition: failed to get virtual port sender after it was dispatched", port); - } - } - RouteResult::Sink => { - trace!("Sending unroutable IP packet received from WireGuard endpoint to sink interface"); - self.route_ip_sink(packet).await.unwrap_or_else(|e| { - error!("Failed to send unroutable IP packet to sink: {:?}", e) - }); - } - RouteResult::Drop => { - trace!("Dropped unroutable IP packet received from WireGuard endpoint"); - } + if let Some(proto) = self.route_protocol(packet) { + endpoint.send(Event::InboundInternetPacket(proto, packet.into())); } } _ => {} @@ -264,89 +217,32 @@ impl WireGuardTunnel { .with_context(|| "Failed to initialize boringtun Tunn") } - /// Makes a decision on the handling of an incoming IP packet. - fn route_ip_packet(&self, packet: &[u8]) -> RouteResult { + /// Determine the inner protocol of the incoming IP packet (TCP/UDP). + fn route_protocol(&self, packet: &[u8]) -> Option { match IpVersion::of_packet(packet) { Ok(IpVersion::Ipv4) => Ipv4Packet::new_checked(&packet) .ok() // Only care if the packet is destined for this tunnel .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip) .map(|packet| match packet.protocol() { - IpProtocol::Tcp => Some(self.route_tcp_segment(packet.payload())), - IpProtocol::Udp => Some(self.route_udp_datagram(packet.payload())), + IpProtocol::Tcp => Some(PortProtocol::Tcp), + IpProtocol::Udp => Some(PortProtocol::Udp), // Unrecognized protocol, so we cannot determine where to route - _ => Some(RouteResult::Drop), + _ => None, }) - .flatten() - .unwrap_or(RouteResult::Drop), + .flatten(), Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet) .ok() // Only care if the packet is destined for this tunnel .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip) .map(|packet| match packet.next_header() { - IpProtocol::Tcp => Some(self.route_tcp_segment(packet.payload())), - IpProtocol::Udp => Some(self.route_udp_datagram(packet.payload())), + IpProtocol::Tcp => Some(PortProtocol::Tcp), + IpProtocol::Udp => Some(PortProtocol::Udp), // Unrecognized protocol, so we cannot determine where to route - _ => Some(RouteResult::Drop), + _ => None, }) - .flatten() - .unwrap_or(RouteResult::Drop), - _ => RouteResult::Drop, - } - } - - /// Makes a decision on the handling of an incoming TCP segment. - fn route_tcp_segment(&self, segment: &[u8]) -> RouteResult { - TcpPacket::new_checked(segment) - .ok() - .map(|tcp| { - if self - .virtual_port_ip_tx - .get(&VirtualPort(tcp.dst_port(), PortProtocol::Tcp)) - .is_some() - { - RouteResult::Dispatch(VirtualPort(tcp.dst_port(), PortProtocol::Tcp)) - } else if tcp.rst() { - RouteResult::Drop - } else { - RouteResult::Sink - } - }) - .unwrap_or(RouteResult::Drop) - } - - /// Makes a decision on the handling of an incoming UDP datagram. - fn route_udp_datagram(&self, datagram: &[u8]) -> RouteResult { - UdpPacket::new_checked(datagram) - .ok() - .map(|udp| { - if self - .virtual_port_ip_tx - .get(&VirtualPort(udp.dst_port(), PortProtocol::Udp)) - .is_some() - { - RouteResult::Dispatch(VirtualPort(udp.dst_port(), PortProtocol::Udp)) - } else { - RouteResult::Drop - } - }) - .unwrap_or(RouteResult::Drop) - } - - /// Route a packet to the IP sink interface. - async fn route_ip_sink(&self, packet: &[u8]) -> anyhow::Result<()> { - let ip_sink_tx = self.sink_ip_tx.read().await; - - if let Some(ip_sink_tx) = &*ip_sink_tx { - ip_sink_tx - .send(packet.to_vec()) - .await - .with_context(|| "Failed to dispatch IP packet to sink interface") - } else { - warn!( - "Could not dispatch unroutable IP packet to sink because interface is not active." - ); - Ok(()) + .flatten(), + _ => None, } } } @@ -370,12 +266,3 @@ fn trace_ip_packet(message: &str, packet: &[u8]) { } } } - -enum RouteResult { - /// Dispatch the packet to the virtual port. - Dispatch(VirtualPort), - /// The packet is not routable, and should be sent to the sink interface. - Sink, - /// The packet is not routable, and can be safely ignored. - Drop, -} From abd9df6be42f6d1fbc219250921dcb6c2e87a517 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 02:25:56 -0500 Subject: [PATCH 051/165] Implement event-based UDP interface --- src/events.rs | 2 +- src/main.rs | 8 +- src/tunnel/tcp.rs | 2 +- src/tunnel/udp.rs | 8 +- src/virtual_iface/mod.rs | 3 +- src/virtual_iface/tcp.rs | 47 +++++----- src/virtual_iface/udp.rs | 198 ++++++++++++++++++++++++++++++++++++--- 7 files changed, 218 insertions(+), 50 deletions(-) diff --git a/src/events.rs b/src/events.rs index 1437bc2..33f8ab9 100644 --- a/src/events.rs +++ b/src/events.rs @@ -15,7 +15,7 @@ pub enum Event { /// A connection was dropped from the pool and should be closed in all interfaces. ClientConnectionDropped(VirtualPort), /// Data received by the local server that should be sent to the virtual server. - LocalData(VirtualPort, Vec), + LocalData(PortForwardConfig, VirtualPort, Vec), /// Data received by the remote server that should be sent to the local client. RemoteData(VirtualPort, Vec), /// IP packet received from the WireGuard tunnel that should be passed through the corresponding virtual device. diff --git a/src/main.rs b/src/main.rs index 1a01b51..2377245 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,8 +72,8 @@ async fn main() -> anyhow::Result<()> { // Start TCP Virtual Interface let port_forwards = config.port_forwards.clone(); - let iface = TcpVirtualInterface::new(port_forwards, bus, device, config.source_peer_ip); - tokio::spawn(async move { iface.poll_loop().await }); + let iface = TcpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); + tokio::spawn(async move { iface.poll_loop(device).await }); } if config @@ -88,8 +88,8 @@ async fn main() -> anyhow::Result<()> { // Start UDP Virtual Interface let port_forwards = config.port_forwards.clone(); - let iface = UdpVirtualInterface::new(port_forwards, bus, device, config.source_peer_ip); - tokio::spawn(async move { iface.poll_loop().await }); + let iface = UdpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); + tokio::spawn(async move { iface.poll_loop(device).await }); } { diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index 718cf57..4633e78 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -90,7 +90,7 @@ async fn handle_tcp_proxy_connection( match socket.try_read_buf(&mut buffer) { Ok(size) if size > 0 => { let data = Vec::from(&buffer[..size]); - endpoint.send(Event::LocalData(virtual_port, data)); + endpoint.send(Event::LocalData(port_forward, virtual_port, data)); // Reset buffer buffer.clear(); } diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index 34fb26d..d9bd43d 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -48,7 +48,7 @@ pub async fn udp_proxy_server( to_send_result = next_udp_datagram(&socket, &mut buffer, port_pool.clone()) => { match to_send_result { Ok(Some((port, data))) => { - endpoint.send(Event::LocalData(port, data)); + endpoint.send(Event::LocalData(port_forward, port, data)); } Ok(None) => { continue; @@ -64,12 +64,12 @@ pub async fn udp_proxy_server( } event = endpoint.recv() => { if let Event::RemoteData(port, data) = event { - if let Some(peer_addr) = port_pool.get_peer_addr(port).await { - if let Err(e) = socket.send_to(&data, peer_addr).await { + if let Some(peer) = port_pool.get_peer_addr(port).await { + if let Err(e) = socket.send_to(&data, peer).await { error!( "[{}] Failed to send UDP datagram to real client ({}): {:?}", port, - peer_addr, + peer, e, ); } diff --git a/src/virtual_iface/mod.rs b/src/virtual_iface/mod.rs index ea3cd74..4fd6f47 100644 --- a/src/virtual_iface/mod.rs +++ b/src/virtual_iface/mod.rs @@ -2,6 +2,7 @@ pub mod tcp; pub mod udp; use crate::config::PortProtocol; +use crate::VirtualIpDevice; use async_trait::async_trait; use std::fmt::{Display, Formatter}; @@ -9,7 +10,7 @@ use std::fmt::{Display, Formatter}; pub trait VirtualInterfacePoll { /// Initializes the virtual interface and processes incoming data to be dispatched /// to the WireGuard tunnel and to the real client. - async fn poll_loop(mut self) -> anyhow::Result<()>; + async fn poll_loop(mut self, device: VirtualIpDevice) -> anyhow::Result<()>; } /// Virtual port. diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 6c283f1..190af1f 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -18,25 +18,18 @@ const MAX_PACKET: usize = 65536; pub struct TcpVirtualInterface { source_peer_ip: IpAddr, port_forwards: Vec, - device: VirtualIpDevice, bus: Bus, } impl TcpVirtualInterface { /// Initialize the parameters for a new virtual interface. /// Use the `poll_loop()` future to start the virtual interface poll loop. - pub fn new( - port_forwards: Vec, - bus: Bus, - device: VirtualIpDevice, - source_peer_ip: IpAddr, - ) -> Self { + pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { Self { port_forwards: port_forwards .into_iter() .filter(|f| matches!(f.protocol, PortProtocol::Tcp)) .collect(), - device, source_peer_ip, bus, } @@ -68,25 +61,28 @@ impl TcpVirtualInterface { let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); Ok(socket) } + + fn addresses(&self) -> Vec { + let mut addresses = HashSet::new(); + addresses.insert(IpAddress::from(self.source_peer_ip)); + for config in self.port_forwards.iter() { + addresses.insert(IpAddress::from(config.destination.ip())); + } + addresses + .into_iter() + .map(|addr| IpCidr::new(addr, 32)) + .collect() + } } #[async_trait] impl VirtualInterfacePoll for TcpVirtualInterface { - async fn poll_loop(self) -> anyhow::Result<()> { + async fn poll_loop(self, device: VirtualIpDevice) -> anyhow::Result<()> { // Create CIDR block for source peer IP + each port forward IP - let addresses: Vec = { - let mut addresses = HashSet::new(); - addresses.insert(IpAddress::from(self.source_peer_ip)); - for config in self.port_forwards.iter() { - addresses.insert(IpAddress::from(config.destination.ip())); - } - addresses - .into_iter() - .map(|addr| IpCidr::new(addr, 32)) - .collect() - }; + let addresses = self.addresses(); - let mut iface = InterfaceBuilder::new(self.device, vec![]) + // Create virtual interface (contains smoltcp state machine) + let mut iface = InterfaceBuilder::new(device, vec![]) .ip_addrs(addresses) .finalize(); @@ -102,6 +98,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { // Bus endpoint to read events let mut endpoint = self.bus.new_endpoint(); + // Maps virtual port to its client socket handle let mut port_client_handle_map: HashMap = HashMap::new(); // Data packets to send from a virtual client @@ -146,10 +143,11 @@ impl VirtualInterfacePoll for TcpVirtualInterface { ); } } + break; } else if client_socket.state() == TcpState::CloseWait { client_socket.close(); + break; } - break; } } } @@ -163,8 +161,6 @@ impl VirtualInterfacePoll for TcpVirtualInterface { if !data.is_empty() { endpoint.send(Event::RemoteData(*virtual_port, data)); break; - } else { - continue; } } Err(e) => { @@ -182,6 +178,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { if client_socket.state() == TcpState::Closed { endpoint.send(Event::ClientConnectionDropped(*virtual_port)); send_queue.remove(virtual_port); + iface.remove_socket(*client_handle); false } else { // Not closed, retain @@ -235,7 +232,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { next_poll = None; } } - Event::LocalData(virtual_port, data) if send_queue.contains_key(&virtual_port) => { + Event::LocalData(_, virtual_port, data) if send_queue.contains_key(&virtual_port) => { if let Some(send_queue) = send_queue.get_mut(&virtual_port) { send_queue.push_back(data); next_poll = None; diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 022a2c2..f1725e7 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -1,49 +1,219 @@ #![allow(dead_code)] + +use anyhow::Context; +use std::collections::{HashMap, HashSet, VecDeque}; use std::net::IpAddr; +use crate::events::Event; use crate::{Bus, PortProtocol}; use async_trait::async_trait; +use smoltcp::iface::{InterfaceBuilder, SocketHandle}; +use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; +use smoltcp::wire::{IpAddress, IpCidr}; +use std::time::Duration; use crate::config::PortForwardConfig; use crate::virtual_device::VirtualIpDevice; -use crate::virtual_iface::VirtualInterfacePoll; +use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; const MAX_PACKET: usize = 65536; pub struct UdpVirtualInterface { source_peer_ip: IpAddr, port_forwards: Vec, - device: VirtualIpDevice, bus: Bus, } impl UdpVirtualInterface { /// Initialize the parameters for a new virtual interface. /// Use the `poll_loop()` future to start the virtual interface poll loop. - pub fn new( - port_forwards: Vec, - bus: Bus, - device: VirtualIpDevice, - source_peer_ip: IpAddr, - ) -> Self { + pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { Self { port_forwards: port_forwards .into_iter() .filter(|f| matches!(f.protocol, PortProtocol::Udp)) .collect(), - device, source_peer_ip, bus, } } + + fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { + static mut UDP_SERVER_RX_META: [UdpPacketMetadata; 0] = []; + static mut UDP_SERVER_RX_DATA: [u8; 0] = []; + static mut UDP_SERVER_TX_META: [UdpPacketMetadata; 0] = []; + static mut UDP_SERVER_TX_DATA: [u8; 0] = []; + let udp_rx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe { + &mut UDP_SERVER_RX_DATA[..] + }); + let udp_tx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe { + &mut UDP_SERVER_TX_DATA[..] + }); + let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); + socket + .bind(( + IpAddress::from(port_forward.destination.ip()), + port_forward.destination.port(), + )) + .with_context(|| "UDP virtual server socket failed to bind")?; + Ok(socket) + } + + fn new_client_socket( + source_peer_ip: IpAddr, + client_port: VirtualPort, + ) -> anyhow::Result> { + let rx_meta = vec![UdpPacketMetadata::EMPTY; 10]; + let tx_meta = vec![UdpPacketMetadata::EMPTY; 10]; + let rx_data = vec![0u8; MAX_PACKET]; + let tx_data = vec![0u8; MAX_PACKET]; + let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data); + let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data); + let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); + socket + .bind((IpAddress::from(source_peer_ip), client_port.num())) + .with_context(|| "UDP virtual client failed to bind")?; + Ok(socket) + } + + fn addresses(&self) -> Vec { + let mut addresses = HashSet::new(); + addresses.insert(IpAddress::from(self.source_peer_ip)); + for config in self.port_forwards.iter() { + addresses.insert(IpAddress::from(config.destination.ip())); + } + addresses + .into_iter() + .map(|addr| IpCidr::new(addr, 32)) + .collect() + } } #[async_trait] impl VirtualInterfacePoll for UdpVirtualInterface { - async fn poll_loop(self) -> anyhow::Result<()> { - // TODO: Create smoltcp virtual device and interface - // TODO: Create smoltcp virtual servers for `port_forwards` - // TODO: listen on events - futures::future::pending().await + async fn poll_loop(self, device: VirtualIpDevice) -> anyhow::Result<()> { + // Create CIDR block for source peer IP + each port forward IP + let addresses = self.addresses(); + + // Create virtual interface (contains smoltcp state machine) + let mut iface = InterfaceBuilder::new(device, vec![]) + .ip_addrs(addresses) + .finalize(); + + // Create virtual server for each port forward + for port_forward in self.port_forwards.iter() { + let server_socket = UdpVirtualInterface::new_server_socket(*port_forward)?; + iface.add_socket(server_socket); + } + + // The next time to poll the interface. Can be None for instant poll. + let mut next_poll: Option = None; + + // Bus endpoint to read events + let mut endpoint = self.bus.new_endpoint(); + + // Maps virtual port to its client socket handle + let mut port_client_handle_map: HashMap = HashMap::new(); + + // Data packets to send from a virtual client + let mut send_queue: HashMap)>> = + HashMap::new(); + + loop { + tokio::select! { + _ = match (next_poll, port_client_handle_map.len()) { + (None, 0) => tokio::time::sleep(Duration::MAX), + (None, _) => tokio::time::sleep(Duration::ZERO), + (Some(until), _) => tokio::time::sleep_until(until), + } => { + let loop_start = smoltcp::time::Instant::now(); + + match iface.poll(loop_start) { + Ok(processed) if processed => { + trace!("UDP virtual interface polled some packets to be processed"); + } + Err(e) => error!("UDP virtual interface poll error: {:?}", e), + _ => {} + } + + // Find client socket send data to + for (virtual_port, client_handle) in port_client_handle_map.iter() { + let client_socket = iface.get_socket::(*client_handle); + if client_socket.can_send() { + if let Some(send_queue) = send_queue.get_mut(virtual_port) { + let to_transfer = send_queue.pop_front(); + if let Some((port_forward, data)) = to_transfer { + client_socket + .send_slice( + &data, + (IpAddress::from(port_forward.destination.ip()), port_forward.destination.port()).into(), + ) + .unwrap_or_else(|e| { + error!( + "[{}] Failed to send data to virtual server: {:?}", + virtual_port, e + ); + }); + break; + } + } + } + } + + // Find client socket recv data from + for (virtual_port, client_handle) in port_client_handle_map.iter() { + let client_socket = iface.get_socket::(*client_handle); + if client_socket.can_recv() { + match client_socket.recv() { + Ok((data, _peer)) => { + if !data.is_empty() { + endpoint.send(Event::RemoteData(*virtual_port, data.to_vec())); + break; + } + } + Err(e) => { + error!( + "Failed to read from virtual client socket: {:?}", e + ); + } + } + } + } + + // The virtual interface determines the next time to poll (this is to reduce unnecessary polls) + next_poll = match iface.poll_delay(loop_start) { + Some(smoltcp::time::Duration::ZERO) => None, + Some(delay) => { + trace!("UDP Virtual interface delayed next poll by {}", delay); + Some(tokio::time::Instant::now() + Duration::from_millis(delay.total_millis())) + }, + None => None, + }; + } + event = endpoint.recv() => { + match event { + Event::LocalData(port_forward, virtual_port, data) => { + if let Some(send_queue) = send_queue.get_mut(&virtual_port) { + // Client socket already exists + send_queue.push_back((port_forward, data)); + } else { + // Client socket does not exist + let client_socket = UdpVirtualInterface::new_client_socket(self.source_peer_ip, virtual_port)?; + let client_handle = iface.add_socket(client_socket); + + // Add handle to map + port_client_handle_map.insert(virtual_port, client_handle); + send_queue.insert(virtual_port, VecDeque::from(vec![(port_forward, data)])); + } + next_poll = None; + } + Event::VirtualDeviceFed(protocol) if protocol == PortProtocol::Udp => { + next_poll = None; + } + _ => {} + } + } + } + } } } From 2e204d80fd7288016d47722c79a57a8afaa214fd Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 03:44:10 -0500 Subject: [PATCH 052/165] release: v0.2.4 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1d4364..d073744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,7 +543,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index accb4e3..a1124cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.3" +version = "0.2.4" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 2b18bd4ec344ba9e8307e4afb0bb44197311a7fb Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 14:41:12 -0500 Subject: [PATCH 053/165] Remove unused dependency. Improve trace logging perf. --- Cargo.lock | 11 ----------- Cargo.toml | 1 - src/events.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d073744..920a630 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,16 +171,6 @@ dependencies = [ "unreachable", ] -[[package]] -name = "dashmap" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" -dependencies = [ - "cfg-if", - "num_cpus", -] - [[package]] name = "either" version = "1.6.1" @@ -549,7 +539,6 @@ dependencies = [ "async-trait", "boringtun", "clap", - "dashmap", "futures", "log", "nom", diff --git a/Cargo.toml b/Cargo.toml index a1124cf..53d4b76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,5 +17,4 @@ futures = "0.3.17" rand = "0.8.4" nom = "7" async-trait = "0.1.51" -dashmap = "4.0.2" priority-queue = "1.2.0" diff --git a/src/events.rs b/src/events.rs index 33f8ab9..7c0a194 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,3 +1,4 @@ +use std::fmt::{Display, Formatter}; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; @@ -26,6 +27,45 @@ pub enum Event { VirtualDeviceFed(PortProtocol), } +impl Display for Event { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Event::Dumb => { + write!(f, "Dumb{{}}") + } + Event::ClientConnectionInitiated(pf, vp) => { + write!(f, "ClientConnectionInitiated{{ pf={} vp={} }}", pf, vp) + } + Event::ClientConnectionDropped(vp) => { + write!(f, "ClientConnectionDropped{{ vp={} }}", vp) + } + Event::LocalData(pf, vp, data) => { + let size = data.len(); + write!(f, "LocalData{{ pf={} vp={} size={} }}", pf, vp, size) + } + Event::RemoteData(vp, data) => { + let size = data.len(); + write!(f, "RemoteData{{ vp={} size={} }}", vp, size) + } + Event::InboundInternetPacket(proto, data) => { + let size = data.len(); + write!( + f, + "InboundInternetPacket{{ proto={} size={} }}", + proto, size + ) + } + Event::OutboundInternetPacket(data) => { + let size = data.len(); + write!(f, "OutboundInternetPacket{{ size={} }}", size) + } + Event::VirtualDeviceFed(proto) => { + write!(f, "VirtualDeviceFed{{ proto={} }}", proto) + } + } + } +} + #[derive(Clone)] pub struct Bus { counter: Arc, @@ -84,7 +124,7 @@ impl BusEndpoint { // If the event was sent by this endpoint, it is skipped continue; } else { - trace!("#{} <- {:?}", self.id, event); + trace!("#{} <- {}", self.id, event); return event; } } @@ -111,7 +151,7 @@ pub struct BusSender { impl BusSender { /// Sends the event on the bus. Note that the messages sent by this endpoint won't reach itself. pub fn send(&self, event: Event) { - trace!("#{} -> {:?}", self.id, event); + trace!("#{} -> {}", self.id, event); match self.tx.send((self.id, event)) { Ok(_) => {} Err(_) => error!("Failed to send event to bus from endpoint #{}", self.id), From 025c001abb91f1127d244648b4a770805c65b3a8 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 14:48:04 -0500 Subject: [PATCH 054/165] Remove event tracing when reading from bus --- src/events.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/events.rs b/src/events.rs index 7c0a194..cd76a49 100644 --- a/src/events.rs +++ b/src/events.rs @@ -124,7 +124,6 @@ impl BusEndpoint { // If the event was sent by this endpoint, it is skipped continue; } else { - trace!("#{} <- {}", self.id, event); return event; } } From daa23629154a1e8c40b0840a32cf9653ffda37e0 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 14:52:06 -0500 Subject: [PATCH 055/165] release: v0.2.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 920a630..1297497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,7 +533,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.4" +version = "0.2.5" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 53d4b76..387882a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.4" +version = "0.2.5" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From d9bccb79e5428854139dda614be5c9d5feb72176 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 15:11:59 -0500 Subject: [PATCH 056/165] Process all TCP virtual client sockets in one poll --- src/virtual_iface/tcp.rs | 43 ++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 190af1f..3005c3c 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -113,6 +113,20 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } => { let loop_start = smoltcp::time::Instant::now(); + // Find closed sockets + port_client_handle_map.retain(|virtual_port, client_handle| { + let client_socket = iface.get_socket::(*client_handle); + if client_socket.state() == TcpState::Closed { + endpoint.send(Event::ClientConnectionDropped(*virtual_port)); + send_queue.remove(virtual_port); + iface.remove_socket(*client_handle); + false + } else { + // Not closed, retain + true + } + }); + match iface.poll(loop_start) { Ok(processed) if processed => { trace!("TCP virtual interface polled some packets to be processed"); @@ -121,7 +135,6 @@ impl VirtualInterfacePoll for TcpVirtualInterface { _ => {} } - // Find client socket send data to for (virtual_port, client_handle) in port_client_handle_map.iter() { let client_socket = iface.get_socket::(*client_handle); if client_socket.can_send() { @@ -143,24 +156,16 @@ impl VirtualInterfacePoll for TcpVirtualInterface { ); } } - break; } else if client_socket.state() == TcpState::CloseWait { client_socket.close(); - break; } } } - } - - // Find client socket recv data from - for (virtual_port, client_handle) in port_client_handle_map.iter() { - let client_socket = iface.get_socket::(*client_handle); if client_socket.can_recv() { match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { Ok(data) => { if !data.is_empty() { endpoint.send(Event::RemoteData(*virtual_port, data)); - break; } } Err(e) => { @@ -172,20 +177,6 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } } - // Find closed sockets - port_client_handle_map.retain(|virtual_port, client_handle| { - let client_socket = iface.get_socket::(*client_handle); - if client_socket.state() == TcpState::Closed { - endpoint.send(Event::ClientConnectionDropped(*virtual_port)); - send_queue.remove(virtual_port); - iface.remove_socket(*client_handle); - false - } else { - // Not closed, retain - true - } - }); - // The virtual interface determines the next time to poll (this is to reduce unnecessary polls) next_poll = match iface.poll_delay(loop_start) { Some(smoltcp::time::Duration::ZERO) => None, @@ -223,11 +214,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } Event::ClientConnectionDropped(virtual_port) => { if let Some(client_handle) = port_client_handle_map.get(&virtual_port) { - let client_handle = *client_handle; - port_client_handle_map.remove(&virtual_port); - send_queue.remove(&virtual_port); - - let client_socket = iface.get_socket::(client_handle); + let client_socket = iface.get_socket::(*client_handle); client_socket.close(); next_poll = None; } From b3776c8b0577ec8b009f9040bf4d80f233f60778 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 15:12:23 -0500 Subject: [PATCH 057/165] release: v0.2.6 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1297497..17d5743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,7 +533,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.5" +version = "0.2.6" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 387882a..ea3a520 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.5" +version = "0.2.6" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 953bc182791148745ba58a49eeca9c46934060be Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 15:20:21 -0500 Subject: [PATCH 058/165] Remove some clippy suppressions for udp files --- src/tunnel/mod.rs | 1 - src/tunnel/udp.rs | 12 ++++-------- src/virtual_iface/udp.rs | 2 -- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index 1676d7a..eadf8b0 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -8,7 +8,6 @@ use crate::tunnel::udp::UdpPortPool; use crate::wg::WireGuardTunnel; pub mod tcp; -#[allow(unused)] pub mod udp; pub async fn port_forward( diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index d9bd43d..76005a9 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -1,22 +1,18 @@ -use std::collections::{BTreeMap, HashMap, VecDeque}; +use std::collections::{HashMap, VecDeque}; use std::net::{IpAddr, SocketAddr}; use std::ops::Range; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Instant; use crate::events::{Bus, Event}; use anyhow::Context; use priority_queue::double_priority_queue::DoublePriorityQueue; -use priority_queue::priority_queue::PriorityQueue; use rand::seq::SliceRandom; use rand::thread_rng; use tokio::net::UdpSocket; use crate::config::{PortForwardConfig, PortProtocol}; -use crate::virtual_iface::udp::UdpVirtualInterface; -use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; -use crate::wg::WireGuardTunnel; +use crate::virtual_iface::VirtualPort; const MAX_PACKET: usize = 65536; const MIN_PORT: u16 = 1000; @@ -210,13 +206,13 @@ impl UdpPortPool { pub async fn update_last_transmit(&self, port: VirtualPort) { let mut inner = self.inner.write().await; if let Some(peer) = inner.peer_addr_by_port.get(&port.num()).copied() { - let mut pq: &mut DoublePriorityQueue = inner + let pq: &mut DoublePriorityQueue = inner .peer_port_usage .entry(peer.ip()) .or_insert_with(Default::default); pq.push(port.num(), Instant::now()); } - let mut pq: &mut DoublePriorityQueue = &mut inner.port_usage; + let pq: &mut DoublePriorityQueue = &mut inner.port_usage; pq.push(port.num(), Instant::now()); } diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index f1725e7..68de77a 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - use anyhow::Context; use std::collections::{HashMap, HashSet, VecDeque}; use std::net::IpAddr; From ff0f5b967e6f64dfb14639d288ad212202c0f852 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 16:05:14 -0500 Subject: [PATCH 059/165] Add optional IP packet capture for WireGuard tunnel --- .gitignore | 1 + README.md | 11 +++++ src/config.rs | 10 ++++- src/main.rs | 7 ++++ src/pcap.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/pcap.rs diff --git a/.gitignore b/.gitignore index 911ee60..0333ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.idea .envrc *.log +*.pcap diff --git a/README.md b/README.md index 02754d4..fbda40f 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,17 @@ INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140. INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` +### Packet Capture + +For debugging purposes, you can enable the capture of IP packets sent between onetun and the WireGuard peer. +The output is a libpcap capture file that can be viewed with Wireshark. + +``` +$ ./onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080 +INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap +INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +``` + ## Download Normally I would publish `onetun` to crates.io. However, it depends on some features diff --git a/src/config.rs b/src/config.rs index 8df9511..e4ea700 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,6 +20,7 @@ pub struct Config { pub(crate) max_transmission_unit: usize, pub(crate) log: String, pub(crate) warnings: Vec, + pub(crate) pcap_file: Option, } impl Config { @@ -96,7 +97,13 @@ impl Config { .long("log") .env("ONETUN_LOG") .default_value("info") - .help("Configures the log level and format.") + .help("Configures the log level and format."), + Arg::with_name("pcap") + .required(false) + .takes_value(true) + .long("pcap") + .env("ONETUN_PCAP") + .help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file.") ]).get_matches(); // Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs @@ -174,6 +181,7 @@ impl Config { max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit")) .with_context(|| "Invalid max-transmission-unit value")?, log: matches.value_of("log").unwrap_or_default().into(), + pcap_file: matches.value_of("pcap").map(String::from), warnings, }) } diff --git a/src/main.rs b/src/main.rs index 2377245..879b45e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use crate::wg::WireGuardTunnel; pub mod config; pub mod events; +pub mod pcap; pub mod tunnel; pub mod virtual_device; pub mod virtual_iface; @@ -37,6 +38,12 @@ async fn main() -> anyhow::Result<()> { let bus = Bus::default(); + if let Some(pcap_file) = config.pcap_file.clone() { + // Start packet capture + let bus = bus.clone(); + tokio::spawn(async move { pcap::capture(pcap_file, bus).await }); + } + let wg = WireGuardTunnel::new(&config, bus.clone()) .await .with_context(|| "Failed to initialize WireGuard tunnel")?; diff --git a/src/pcap.rs b/src/pcap.rs new file mode 100644 index 0000000..1771a33 --- /dev/null +++ b/src/pcap.rs @@ -0,0 +1,113 @@ +use crate::events::Event; +use crate::Bus; +use anyhow::Context; +use smoltcp::time::Instant; +use tokio::fs::File; +use tokio::io::{AsyncWriteExt, BufWriter}; + +struct Pcap { + writer: BufWriter, +} + +/// libpcap file writer +/// This is mostly taken from `smoltcp`, but rewritten to be async. +impl Pcap { + async fn flush(&mut self) -> anyhow::Result<()> { + self.writer + .flush() + .await + .with_context(|| "Failed to flush pcap writer") + } + + async fn write(&mut self, data: &[u8]) -> anyhow::Result { + self.writer + .write(data) + .await + .with_context(|| format!("Failed to write {} bytes to pcap writer", data.len())) + } + + async fn write_u16(&mut self, value: u16) -> anyhow::Result<()> { + self.writer + .write_u16(value) + .await + .with_context(|| "Failed to write u16 to pcap writer") + } + + async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> { + self.writer + .write_u32(value) + .await + .with_context(|| "Failed to write u32 to pcap writer") + } + + async fn global_header(&mut self) -> anyhow::Result<()> { + self.write_u32(0xa1b2c3d4).await?; // magic number + self.write_u16(2).await?; // major version + self.write_u16(4).await?; // minor version + self.write_u32(0).await?; // timezone (= UTC) + self.write_u32(0).await?; // accuracy (not used) + self.write_u32(65535).await?; // maximum packet length + self.write_u32(101).await?; // link-layer header type (101 = IP) + self.flush().await + } + + async fn packet_header(&mut self, timestamp: Instant, length: usize) -> anyhow::Result<()> { + assert!(length <= 65535); + + self.write_u32(timestamp.secs() as u32).await?; // timestamp seconds + self.write_u32(timestamp.micros() as u32).await?; // timestamp microseconds + self.write_u32(length as u32).await?; // captured length + self.write_u32(length as u32).await?; // original length + Ok(()) + } + + async fn packet(&mut self, timestamp: Instant, packet: &[u8]) -> anyhow::Result<()> { + self.packet_header(timestamp, packet.len()) + .await + .with_context(|| "Failed to write packet header to pcap writer")?; + self.write(packet) + .await + .with_context(|| "Failed to write packet to pcap writer")?; + self.writer + .flush() + .await + .with_context(|| "Failed to flush pcap writer")?; + self.flush().await + } +} + +/// Listens on the event bus for IP packets sent from and to the WireGuard tunnel. +pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> { + let mut endpoint = bus.new_endpoint(); + let file = File::create(&pcap_file) + .await + .with_context(|| "Failed to create pcap file")?; + let writer = BufWriter::new(file); + + let mut writer = Pcap { writer }; + writer + .global_header() + .await + .with_context(|| "Failed to write global header to pcap writer")?; + + info!("Capturing WireGuard IP packets to {}", &pcap_file); + loop { + match endpoint.recv().await { + Event::InboundInternetPacket(_proto, ip) => { + let instant = Instant::now(); + writer + .packet(instant, &ip) + .await + .with_context(|| "Failed to write inbound IP packet to pcap writer")?; + } + Event::OutboundInternetPacket(ip) => { + let instant = Instant::now(); + writer + .packet(instant, &ip) + .await + .with_context(|| "Failed to write output IP packet to pcap writer")?; + } + _ => {} + } + } +} From 3b296d66c503217762cf836356db32274ef48cd7 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 17:40:29 -0500 Subject: [PATCH 060/165] release: v0.2.7 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17d5743..f766582 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,7 +533,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.6" +version = "0.2.7" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index ea3a520..8c06b4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.6" +version = "0.2.7" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From e06b6526b7e439227e9b413e079ba9959ba74a44 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 17:49:07 -0500 Subject: [PATCH 061/165] Process more than one UDP socket per poll --- src/virtual_iface/udp.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 68de77a..67b227f 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -152,7 +152,6 @@ impl VirtualInterfacePoll for UdpVirtualInterface { virtual_port, e ); }); - break; } } } @@ -166,7 +165,6 @@ impl VirtualInterfacePoll for UdpVirtualInterface { Ok((data, _peer)) => { if !data.is_empty() { endpoint.send(Event::RemoteData(*virtual_port, data.to_vec())); - break; } } Err(e) => { From def5f22d3c553cf4c146372e9aee05fbae6221f3 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 8 Jan 2022 17:50:29 -0500 Subject: [PATCH 062/165] release: v0.2.8 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f766582..9694807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,7 +533,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.7" +version = "0.2.8" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 8c06b4a..ebe0a6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.7" +version = "0.2.8" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 11f86c49d6609ff659a0cdbc9a82ee9c6b560140 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 9 Jan 2022 22:52:48 -0500 Subject: [PATCH 063/165] Ensure all bytes are written to TcpStream Fixes #22 --- src/tunnel/tcp.rs | 25 ++++++++++++++++++++----- src/virtual_iface/tcp.rs | 1 + 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index 4633e78..e9644b9 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -123,13 +123,28 @@ async fn handle_tcp_proxy_connection( } Event::RemoteData(e_vp, data) if e_vp == virtual_port => { // Have remote data to send to the local client - let size = data.len(); - match socket.write(&data).await { - Ok(size) => debug!("[{}] Sent {} bytes to local client", virtual_port, size), - Err(e) => { - error!("[{}] Failed to send {} bytes to local client: {:?}", virtual_port, size, e); + if let Err(e) = socket.writable().await { + error!("[{}] Failed to check if writable: {:?}", virtual_port, e); + } + let expected = data.len(); + let mut sent = 0; + loop { + if sent >= expected { break; } + match socket.write(&data[sent..expected]).await { + Ok(written) => { + debug!("[{}] Sent {} (expected {}) bytes to local client", virtual_port, written, expected); + sent += written; + if sent < expected { + debug!("[{}] Will try to resend remaining {} bytes to local client", virtual_port, (expected - written)); + } + }, + Err(e) => { + error!("[{}] Failed to send {} bytes to local client: {:?}", virtual_port, expected, e); + break; + } + } } } _ => {} diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 3005c3c..28eacac 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -164,6 +164,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { if client_socket.can_recv() { match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { Ok(data) => { + debug!("[{}] Received {} bytes from virtual server", virtual_port, data.len()); if !data.is_empty() { endpoint.send(Event::RemoteData(*virtual_port, data)); } From 2b15e581f2b83c8049f4f5fe75d724cd90331ce0 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 9 Jan 2022 22:58:16 -0500 Subject: [PATCH 064/165] release: v0.2.9 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9694807..a423ebd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,7 +533,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.8" +version = "0.2.9" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index ebe0a6f..02a45ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.2.8" +version = "0.2.9" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 782f5e74bf2ea0c8b57d5c461b74c079ace46079 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 10 Jan 2022 00:35:14 -0500 Subject: [PATCH 065/165] Apply TcpStream fix to UdpSocket as well --- src/tunnel/udp.rs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index 76005a9..1d25914 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -59,17 +59,33 @@ pub async fn udp_proxy_server( } } event = endpoint.recv() => { - if let Event::RemoteData(port, data) = event { - if let Some(peer) = port_pool.get_peer_addr(port).await { - if let Err(e) = socket.send_to(&data, peer).await { - error!( - "[{}] Failed to send UDP datagram to real client ({}): {:?}", - port, - peer, - e, - ); + if let Event::RemoteData(virtual_port, data) = event { + if let Some(peer) = port_pool.get_peer_addr(virtual_port).await { + // Have remote data to send to the local client + if let Err(e) = socket.writable().await { + error!("[{}] Failed to check if writable: {:?}", virtual_port, e); } - port_pool.update_last_transmit(port).await; + let expected = data.len(); + let mut sent = 0; + loop { + if sent >= expected { + break; + } + match socket.send_to(&data[sent..expected], peer).await { + Ok(written) => { + debug!("[{}] Sent {} (expected {}) bytes to local client", virtual_port, written, expected); + sent += written; + if sent < expected { + debug!("[{}] Will try to resend remaining {} bytes to local client", virtual_port, (expected - written)); + } + }, + Err(e) => { + error!("[{}] Failed to send {} bytes to local client: {:?}", virtual_port, expected, e); + break; + } + } + } + port_pool.update_last_transmit(virtual_port).await; } } } From 47c6c588d25f37d0c42a60231fa5ae82f64f058e Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 10 Jan 2022 00:46:52 -0500 Subject: [PATCH 066/165] udp: remove extra socket iteration in virtual iface --- src/virtual_iface/udp.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 67b227f..d939132 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -134,7 +134,6 @@ impl VirtualInterfacePoll for UdpVirtualInterface { _ => {} } - // Find client socket send data to for (virtual_port, client_handle) in port_client_handle_map.iter() { let client_socket = iface.get_socket::(*client_handle); if client_socket.can_send() { @@ -155,11 +154,6 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } } } - } - - // Find client socket recv data from - for (virtual_port, client_handle) in port_client_handle_map.iter() { - let client_socket = iface.get_socket::(*client_handle); if client_socket.can_recv() { match client_socket.recv() { Ok((data, _peer)) => { From 45962f435645ed70f2f01f2d530f5a44082d6b41 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 10 Jan 2022 01:25:56 -0500 Subject: [PATCH 067/165] Update Architecture section in README --- README.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fbda40f..197d019 100644 --- a/README.md +++ b/README.md @@ -171,20 +171,27 @@ $ ./target/release/onetun ## Architecture +**In short:** onetun uses [smoltcp's](https://github.com/smoltcp-rs/smoltcp) TCP/IP and UDP stack to generate IP packets +using its state machine ("virtual interface"). The generated IP packets are +encrypted by [boringtun](https://github.com/cloudflare/boringtun) and sent to the WireGuard endpoint. Encrypted IP packets received +from the WireGuard endpoint are decrypted using boringtun and sent through the smoltcp virtual interface state machine. +onetun creates "virtual sockets" in the virtual interface to forward data sent from inbound connections, +as well as to receive data from the virtual interface to forward back to the local client. + +--- + onetun uses [tokio](https://github.com/tokio-rs/tokio), the async runtime, to listen for new TCP connections on the given port. -When a client connects to the local TCP port, it uses [smoltcp](https://github.com/smoltcp-rs/smoltcp) to -create a "virtual interface", with a "virtual client" and a "virtual server" for the connection. These "virtual" -components are the crux of how onetun works. They essentially replace the host's TCP/IP stack with smoltcp's, which -fully runs inside onetun. An ephemeral "virtual port" is also assigned to the connection, in order to route packets -back to the right connection. +When a client connects to the onetun's TCP port, a "virtual client" is +created in a [smoltcp](https://github.com/smoltcp-rs/smoltcp) "virtual" TCP/IP interface, which runs fully inside the onetun +process. An ephemeral "virtual port" is assigned to the "virtual client", which maps back to the local client. -When the real client opens the connection, the virtual client socket opens a TCP connection to the virtual server. -The virtual interface (implemented by smoltcp) in turn crafts the `SYN` segment and wraps it in an IP packet. +When the real client opens the connection, the virtual client socket opens a TCP connection to the virtual server +(a dummy socket bound to the remote host/port). The virtual interface in turn crafts the `SYN` segment and wraps it in an IP packet. Because of how the virtual client and server are configured, the IP packet is crafted with a source address being the configured `source-peer-ip` (`192.168.4.3` in the example above), -and the destination address is the remote peer's (`192.168.4.2`). +and the destination address matches the port-forward's configured destination (`192.168.4.2`). By doing this, we let smoltcp handle the crafting of the IP packets, and the handling of the client's TCP states. Instead of actually sending those packets to the virtual server, @@ -195,8 +202,8 @@ Once the WireGuard endpoint receives an encrypted IP packet, it decrypts it usin It reads the destination address, re-encrypts the IP packet using the matching peer's public key, and sends it off to the peer's UDP endpoint. -The remote peer receives the encrypted IP and decrypts it. It can then read the inner payload (the TCP segment), -forward it to the server's port, which handles the TCP segment. The server responds with `SYN-ACK`, which goes back through +The peer receives the encrypted IP and decrypts it. It can then read the inner payload (the TCP segment), +forward it to the server's port, which handles the TCP segment. The TCP server responds with `SYN-ACK`, which goes back through the peer's local WireGuard interface, gets encrypted, forwarded to the WireGuard endpoint, and then finally back to onetun's UDP port. When onetun receives an encrypted packet from the WireGuard endpoint, it decrypts it using boringtun. From 648154b5ee89b4311f24a4827847ead765e8533f Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 10 Jan 2022 01:32:55 -0500 Subject: [PATCH 068/165] Add tcpdump example --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 197d019..f1c5df8 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,12 @@ INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` +To capture packets sent to and from the onetun local port, you must use an external tool like `tcpdump` with root access: + +``` +$ sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8443' +``` + ## Download Normally I would publish `onetun` to crates.io. However, it depends on some features From 93116fae266441c31c8df00beb653cde8b372003 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 15 Feb 2022 01:31:41 -0500 Subject: [PATCH 069/165] Update boringtun to 0.4.0 --- Cargo.lock | 272 ++++++++++++++++++++++++++++------------------------- Cargo.toml | 2 +- 2 files changed, 146 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a423ebd..2dfa9bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" version = "0.7.18" @@ -32,12 +17,6 @@ version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" -[[package]] -name = "ascii" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" - [[package]] name = "async-trait" version = "0.1.52" @@ -66,21 +45,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "backtrace" -version = "0.3.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.13.0" @@ -95,8 +59,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "boringtun" -version = "0.3.0" -source = "git+https://github.com/cloudflare/boringtun?rev=fbcf2689e7776a5af805c5a38feb5c8988829980#fbcf2689e7776a5af805c5a38feb5c8988829980" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcaf3e6d388237249ec234ec6890bfc68634043f9b048011de8d0fc0025c3698" dependencies = [ "base64", "hex", @@ -104,10 +69,10 @@ dependencies = [ "ip_network_table", "jni", "libc", - "parking_lot", + "parking_lot 0.12.0", "ring", - "slog", - "untrusted", + "tracing", + "untrusted 0.9.0", ] [[package]] @@ -160,23 +125,14 @@ dependencies = [ [[package]] name = "combine" -version = "3.8.1" +version = "4.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" dependencies = [ - "ascii", - "byteorder", - "either", + "bytes", "memchr", - "unreachable", ] -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - [[package]] name = "env_logger" version = "0.7.1" @@ -190,16 +146,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "backtrace", - "version_check", -] - [[package]] name = "futures" version = "0.3.19" @@ -300,12 +246,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" - [[package]] name = "hashbrown" version = "0.11.2" @@ -357,15 +297,15 @@ dependencies = [ [[package]] name = "ip_network" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee15951c035f79eddbef745611ec962f63f4558f1dadf98ab723cc603487c6f" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" [[package]] name = "ip_network_table" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa59c26e61245c366c7d615b56524cdc7c9104ba15c0ebfc0f09ddb8cdf728be" +checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" dependencies = [ "ip_network", "ip_network_table-deps-treebitmap", @@ -379,15 +319,15 @@ checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" [[package]] name = "jni" -version = "0.10.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecfa3b81afc64d9a6539c4eece96ac9a93c551c713a313800dade8e33d7b5c1" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" dependencies = [ "cesu8", "combine", - "error-chain", "jni-sys", "log", + "thiserror", "walkdir", ] @@ -420,9 +360,9 @@ checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -454,16 +394,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - [[package]] name = "mio" version = "0.7.14" @@ -516,15 +446,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.9.0" @@ -557,7 +478,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.1", ] [[package]] @@ -574,6 +505,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "pin-project-lite" version = "0.2.7" @@ -712,17 +656,11 @@ dependencies = [ "libc", "once_cell", "spin", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - [[package]] name = "same-file" version = "1.0.6" @@ -753,12 +691,6 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" -[[package]] -name = "slog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" - [[package]] name = "smallvec" version = "1.7.0" @@ -819,6 +751,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.15.0" @@ -831,7 +783,7 @@ dependencies = [ "mio", "num_cpus", "once_cell", - "parking_lot", + "parking_lot 0.11.2", "pin-project-lite", "signal-hook-registry", "tokio-macros", @@ -849,6 +801,38 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", +] + [[package]] name = "unicode-width" version = "0.1.9" @@ -861,33 +845,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] - [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "version_check" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "walkdir" version = "2.3.2" @@ -999,3 +974,46 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" diff --git a/Cargo.toml b/Cargo.toml index 02a45ad..558d20a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "fbcf2689e7776a5af805c5a38feb5c8988829980", default-features = false } +boringtun = { version = "0.4.0", default-features = false } clap = { version = "2.33", default-features = false, features = ["suggestions"] } log = "0.4" pretty_env_logger = "0.4" From a44b8b48eb9c4735b598b4eefac7246197e4054b Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 15 Feb 2022 01:41:13 -0500 Subject: [PATCH 070/165] Update README --- README.md | 69 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f1c5df8..676091d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations. +[![crates.io](https://img.shields.io/crates/v/onetun.svg)](https://crates.io/crates/onetun) +[![MIT licensed](https://img.shields.io/crates/l/onetun.svg)](./LICENSE) [![Build status](https://github.com/aramperes/onetun/actions/workflows/build.yml/badge.svg)](https://github.com/aramperes/onetun/actions) [![Latest Release](https://img.shields.io/github/v/tag/aramperes/onetun?label=release)](https://github.com/aramperes/onetun/releases/latest) @@ -17,6 +19,32 @@ A cross-platform, user-space WireGuard port-forwarder that requires no system ne For example, this can be useful to forward a port from a Kubernetes cluster to a server behind WireGuard, without needing to install WireGuard in a Pod. +## Download + +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.55: + +```shell +$ cargo install onetun +``` + +You can also download the binary for Windows, macOS (Intel), and Linux (amd64) from +the [Releases](https://github.com/aramperes/onetun/releases) page. + +You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun): + +```shell +docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ + 0.0.0.0:8080:192.168.4.2:8080 [...options...] +``` + +You can also build onetun locally, using Rust ≥1.55: + +```shell +$ git clone https://github.com/aramperes/onetun && cd onetun +$ cargo build --release +$ ./target/release/onetun +``` + ## Usage **onetun** opens a TCP or UDP port on your local system, from which traffic is forwarded to a port on a peer in your @@ -27,7 +55,7 @@ The only prerequisite is to register a peer IP and public key on the remote Wire the WireGuard endpoint to trust the onetun peer and for packets to be routed. ``` -./onetun [src_host:]::[:TCP,UDP,...] [...] \ +onetun [src_host:]::[:TCP,UDP,...] [...] \ --endpoint-addr \ --endpoint-public-key \ --private-key \ @@ -65,7 +93,7 @@ We want to access a web server on the friendly peer (`192.168.4.2`) on port `808 local port, say `127.0.0.1:8080`, that will tunnel through WireGuard to reach the peer web server: ```shell -./onetun 127.0.0.1:8080:192.168.4.2:8080 \ +onetun 127.0.0.1:8080:192.168.4.2:8080 \ --endpoint-addr 140.30.3.182:51820 \ --endpoint-public-key 'PUB_****************************************' \ --private-key 'PRIV_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' \ @@ -91,7 +119,7 @@ Hello world! **onetun** supports running multiple tunnels in parallel. For example: ``` -$ ./onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081 +$ onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081 INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -104,10 +132,10 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [ both protocols on the same port (note that this opens 2 separate tunnels, just on the same port) ``` -$ ./onetun 127.0.0.1:8080:192.168.4.2:8080:UDP +$ onetun 127.0.0.1:8080:192.168.4.2:8080:UDP INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -$ ./onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP +$ onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -120,7 +148,7 @@ it in any production capacity. **onetun** supports both IPv4 and IPv6. In fact, you can use onetun to forward some IP version to another, e.g. 6-to-4: ``` -$ ./onetun [::1]:8080:192.168.4.2:8080 +$ onetun [::1]:8080:192.168.4.2:8080 INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -128,7 +156,7 @@ Note that each tunnel can only support one "source" IP version and one "destinat both IPv4 and IPv6 on the same port, you should create a second port-forward: ``` -$ ./onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080 +$ onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080 INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -139,7 +167,7 @@ For debugging purposes, you can enable the capture of IP packets sent between on The output is a libpcap capture file that can be viewed with Wireshark. ``` -$ ./onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080 +$ onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080 INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -150,31 +178,6 @@ To capture packets sent to and from the onetun local port, you must use an exter $ sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8443' ``` -## Download - -Normally I would publish `onetun` to crates.io. However, it depends on some features -in [smoltcp](https://github.com/smoltcp-rs/smoltcp) and -[boringtun](https://github.com/cloudflare/boringtun) that haven't been published yet, so I'm forced to use their Git -repos as dependencies for now. - -In the meantime, you can download the binary for Windows, macOS (Intel), and Linux (amd64) from -the [Releases](https://github.com/aramperes/onetun/releases) page. - -You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun): - -```shell -docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ - 0.0.0.0:8080:192.168.4.2:8080 [...options...] -``` - -You can also build onetun locally, using Rust: - -```shell -$ git clone https://github.com/aramperes/onetun && cd onetun -$ cargo build --release -$ ./target/release/onetun -``` - ## Architecture **In short:** onetun uses [smoltcp's](https://github.com/smoltcp-rs/smoltcp) TCP/IP and UDP stack to generate IP packets From 7ebf8e073797818185c1a5c2e5c4049a72cf6582 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 15 Feb 2022 01:46:01 -0500 Subject: [PATCH 071/165] release: v0.3.0 --- Cargo.lock | 2 +- Cargo.toml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2dfa9bf..b148531 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,7 +454,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.2.9" +version = "0.3.0" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 558d20a..aeeb8fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "onetun" -version = "0.2.9" +version = "0.3.0" edition = "2018" +license = "MIT" +description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." +authors = ["Aram Peres "] +repository = "https://github.com/aramperes/onetun" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 472a4df69fa017bebcfcf9a4c048355931d0ca1d Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 27 Mar 2022 14:15:25 -0400 Subject: [PATCH 072/165] README adjustments --- LICENSE | 2 +- README.md | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/LICENSE b/LICENSE index 7479b85..72d8434 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Aram Peres +Copyright (c) 2021-2022 Aram Peres Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 676091d..f7fe03a 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ A cross-platform, user-space WireGuard port-forwarder that requires no system ne - You want to access this TCP or UDP service from a second computer, on which you can't install WireGuard because you can't (no root access) or don't want to (polluting OS configs). -For example, this can be useful to forward a port from a Kubernetes cluster to a server behind WireGuard, -without needing to install WireGuard in a Pod. +For example, this can be useful to access a port on your WireGuard network from a dev machine that doesn't have WireGuard installed. ## Download @@ -33,8 +32,8 @@ the [Releases](https://github.com/aramperes/onetun/releases) page. You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun): ```shell -docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ - 0.0.0.0:8080:192.168.4.2:8080 [...options...] +$ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ + 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` You can also build onetun locally, using Rust ≥1.55: @@ -61,7 +60,7 @@ onetun [src_host:]::[:TCP,UDP,...] [...] \ --private-key \ --source-peer-ip \ --keep-alive \ - --log ``` > Note: you can use environment variables for all of these flags. Use `onetun --help` for details. @@ -175,7 +174,7 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [ To capture packets sent to and from the onetun local port, you must use an external tool like `tcpdump` with root access: ``` -$ sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8443' +$ sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8080' ``` ## Architecture @@ -244,4 +243,4 @@ All in all, I would not recommend using UDP forwarding for public services, sinc ## License -MIT. See `LICENSE` for details. +MIT License. See `LICENSE` for details. Copyright © 2021-2022 Aram Peres. From 52aba0115d41defac6f21cdb060bd8ba502118f0 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 27 Mar 2022 17:14:13 -0400 Subject: [PATCH 073/165] Fix new clippy lint --- src/config.rs | 3 +-- src/wg.rs | 10 ++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index e4ea700..f4e38e7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -138,8 +138,7 @@ impl Config { // Read private key from file or CLI argument let (group_readable, world_readable) = matches .value_of("private-key-file") - .map(is_file_insecurely_readable) - .flatten() + .and_then(is_file_insecurely_readable) .unwrap_or_default(); if group_readable { warnings.push("Private key file is group-readable. This is insecure.".into()); diff --git a/src/wg.rs b/src/wg.rs index 60767e3..1d06444 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -224,24 +224,22 @@ impl WireGuardTunnel { .ok() // Only care if the packet is destined for this tunnel .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip) - .map(|packet| match packet.protocol() { + .and_then(|packet| match packet.protocol() { IpProtocol::Tcp => Some(PortProtocol::Tcp), IpProtocol::Udp => Some(PortProtocol::Udp), // Unrecognized protocol, so we cannot determine where to route _ => None, - }) - .flatten(), + }), Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet) .ok() // Only care if the packet is destined for this tunnel .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip) - .map(|packet| match packet.next_header() { + .and_then(|packet| match packet.next_header() { IpProtocol::Tcp => Some(PortProtocol::Tcp), IpProtocol::Udp => Some(PortProtocol::Udp), // Unrecognized protocol, so we cannot determine where to route _ => None, - }) - .flatten(), + }), _ => None, } } From 73671a4d07bce45db354dbc9d9831d4bc0c5954c Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Tue, 21 Jun 2022 18:38:55 -0400 Subject: [PATCH 074/165] Add argument and env variable for remote port forwarding. Part of #6 --- src/config.rs | 172 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 33 deletions(-) diff --git a/src/config.rs b/src/config.rs index f4e38e7..eb48510 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,9 +9,13 @@ use anyhow::Context; use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; use clap::{App, Arg}; +const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; + #[derive(Clone, Debug)] pub struct Config { pub(crate) port_forwards: Vec, + #[allow(dead_code)] + pub(crate) remote_port_forwards: Vec, pub(crate) private_key: Arc, pub(crate) endpoint_public_key: Arc, pub(crate) endpoint_addr: SocketAddr, @@ -103,7 +107,23 @@ impl Config { .takes_value(true) .long("pcap") .env("ONETUN_PCAP") - .help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file.") + .help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file."), + Arg::with_name("remote") + .required(false) + .takes_value(true) + .multiple(true) + .long("remote") + .short("r") + .help("Remote port forward configurations. The format of each argument is ::[:TCP,UDP,...], \ + where is the port the other peers will reach the server with, is the IP to forward to, and is the port to forward to. \ + The will be bound on onetun's peer IP, as specified by --source-peer-ip. If you pass a different value for here, it will be rejected.\n\ + Note: : must be reachable by onetun. If referring to another WireGuard peer, use --bridge instead (not supported yet).\n\ + Environment variables of the form 'ONETUN_REMOTE_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1.\n\ + Examples:\n\ + \t--remote 8080:localhost:8081:TCP,UDP\n\ + \t--remote 8080:[::1]:8081:TCP\n\ + \t--remote 8080:google.com:80\ + "), ]).get_matches(); // Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs @@ -120,14 +140,11 @@ impl Config { break; } } - if port_forward_strings.is_empty() { - return Err(anyhow::anyhow!("No port forward configurations given.")); - } // Parse `PORT_FORWARD` strings into `PortForwardConfig` let port_forwards: anyhow::Result>> = port_forward_strings .into_iter() - .map(|s| PortForwardConfig::from_notation(&s)) + .map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE)) .collect(); let port_forwards: Vec = port_forwards .with_context(|| "Failed to parse port forward config")? @@ -135,6 +152,52 @@ impl Config { .flatten() .collect(); + // Read source-peer-ip + let source_peer_ip = parse_ip(matches.value_of("source-peer-ip")) + .with_context(|| "Invalid source peer IP")?; + + // Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs + let mut port_forward_strings = HashSet::new(); + if let Some(values) = matches.values_of("remote") { + for value in values { + port_forward_strings.insert(value.to_owned()); + } + } + for n in 1.. { + if let Ok(env) = std::env::var(format!("ONETUN_REMOTE_PORT_FORWARD_{}", n)) { + port_forward_strings.insert(env); + } else { + break; + } + } + // Parse `PORT_FORWARD` strings into `PortForwardConfig` + let remote_port_forwards: anyhow::Result>> = + port_forward_strings + .into_iter() + .map(|s| { + PortForwardConfig::from_notation( + &s, + matches.value_of("source-peer-ip").unwrap(), + ) + }) + .collect(); + let mut remote_port_forwards: Vec = remote_port_forwards + .with_context(|| "Failed to parse remote port forward config")? + .into_iter() + .flatten() + .collect(); + for port_forward in remote_port_forwards.iter_mut() { + if port_forward.source.ip() != source_peer_ip { + return Err(anyhow::anyhow!("Remote port forward config must match --source-peer-ip ({}), or be omitted.", source_peer_ip)); + } + port_forward.source = SocketAddr::from((source_peer_ip, port_forward.source.port())); + port_forward.remote = true; + } + + if port_forwards.is_empty() && remote_port_forwards.is_empty() { + return Err(anyhow::anyhow!("No port forward configurations given.")); + } + // Read private key from file or CLI argument let (group_readable, world_readable) = matches .value_of("private-key-file") @@ -164,6 +227,7 @@ impl Config { Ok(Self { port_forwards, + remote_port_forwards, private_key: Arc::new( parse_private_key(&private_key).with_context(|| "Invalid private key")?, ), @@ -173,8 +237,7 @@ impl Config { ), endpoint_addr: parse_addr(matches.value_of("endpoint-addr")) .with_context(|| "Invalid endpoint address")?, - source_peer_ip: parse_ip(matches.value_of("source-peer-ip")) - .with_context(|| "Invalid source peer IP")?, + source_peer_ip, keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) .with_context(|| "Invalid keep-alive value")?, max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit")) @@ -255,6 +318,8 @@ pub struct PortForwardConfig { pub destination: SocketAddr, /// The transport protocol to use for the port (Layer 4). pub protocol: PortProtocol, + /// Whether this is a remote port forward. + pub remote: bool, } impl PortForwardConfig { @@ -277,7 +342,7 @@ impl PortForwardConfig { /// - IPv6 addresses must be prefixed with `[` and suffixed with `]`. Example: `[::1]`. /// - Any `u16` is accepted as `src_port` and `dst_port` /// - Specifying protocols (`PROTO1,PROTO2,...`) is optional and defaults to `TCP`. Values must be separated by commas. - pub fn from_notation(s: &str) -> anyhow::Result> { + pub fn from_notation(s: &str, default_source: &str) -> anyhow::Result> { mod parsers { use nom::branch::alt; use nom::bytes::complete::is_not; @@ -355,7 +420,7 @@ impl PortForwardConfig { .1; let source = ( - src_addr.0.unwrap_or("127.0.0.1"), + src_addr.0.unwrap_or(default_source), src_addr .1 .parse::() @@ -395,6 +460,7 @@ impl PortForwardConfig { source, destination, protocol, + remote: false, }) .collect()) } @@ -402,7 +468,15 @@ impl PortForwardConfig { impl Display for PortForwardConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}:{}", self.source, self.destination, self.protocol) + if self.remote { + write!( + f, + "(remote){}:{}:{}", + self.source, self.destination, self.protocol + ) + } else { + write!(f, "{}:{}:{}", self.source, self.destination, self.protocol) + } } } @@ -450,18 +524,23 @@ mod tests { #[test] fn test_parse_port_forward_config_1() { assert_eq!( - PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP,UDP") - .expect("Failed to parse"), + PortForwardConfig::from_notation( + "192.168.0.1:8080:192.168.4.1:8081:TCP,UDP", + DEFAULT_PORT_FORWARD_SOURCE + ) + .expect("Failed to parse"), vec![ PortForwardConfig { source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }, PortForwardConfig { source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Udp + protocol: PortProtocol::Udp, + remote: false, } ] ); @@ -470,12 +549,16 @@ mod tests { #[test] fn test_parse_port_forward_config_2() { assert_eq!( - PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP") - .expect("Failed to parse"), + PortForwardConfig::from_notation( + "192.168.0.1:8080:192.168.4.1:8081:TCP", + DEFAULT_PORT_FORWARD_SOURCE + ) + .expect("Failed to parse"), vec![PortForwardConfig { source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }] ); } @@ -483,12 +566,16 @@ mod tests { #[test] fn test_parse_port_forward_config_3() { assert_eq!( - PortForwardConfig::from_notation("0.0.0.0:8080:192.168.4.1:8081") - .expect("Failed to parse"), + PortForwardConfig::from_notation( + "0.0.0.0:8080:192.168.4.1:8081", + DEFAULT_PORT_FORWARD_SOURCE + ) + .expect("Failed to parse"), vec![PortForwardConfig { source: SocketAddr::from_str("0.0.0.0:8080").unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }] ); } @@ -496,12 +583,16 @@ mod tests { #[test] fn test_parse_port_forward_config_4() { assert_eq!( - PortForwardConfig::from_notation("[::1]:8080:192.168.4.1:8081") - .expect("Failed to parse"), + PortForwardConfig::from_notation( + "[::1]:8080:192.168.4.1:8081", + DEFAULT_PORT_FORWARD_SOURCE + ) + .expect("Failed to parse"), vec![PortForwardConfig { source: SocketAddr::from_str("[::1]:8080").unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }] ); } @@ -509,11 +600,13 @@ mod tests { #[test] fn test_parse_port_forward_config_5() { assert_eq!( - PortForwardConfig::from_notation("8080:192.168.4.1:8081").expect("Failed to parse"), + PortForwardConfig::from_notation("8080:192.168.4.1:8081", DEFAULT_PORT_FORWARD_SOURCE) + .expect("Failed to parse"), vec![PortForwardConfig { source: SocketAddr::from_str("127.0.0.1:8080").unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }] ); } @@ -521,11 +614,16 @@ mod tests { #[test] fn test_parse_port_forward_config_6() { assert_eq!( - PortForwardConfig::from_notation("8080:192.168.4.1:8081:TCP").expect("Failed to parse"), + PortForwardConfig::from_notation( + "8080:192.168.4.1:8081:TCP", + DEFAULT_PORT_FORWARD_SOURCE + ) + .expect("Failed to parse"), vec![PortForwardConfig { source: SocketAddr::from_str("127.0.0.1:8080").unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }] ); } @@ -533,12 +631,16 @@ mod tests { #[test] fn test_parse_port_forward_config_7() { assert_eq!( - PortForwardConfig::from_notation("localhost:8080:192.168.4.1:8081") - .expect("Failed to parse"), + PortForwardConfig::from_notation( + "localhost:8080:192.168.4.1:8081", + DEFAULT_PORT_FORWARD_SOURCE + ) + .expect("Failed to parse"), vec![PortForwardConfig { source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(), destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }] ); } @@ -546,12 +648,16 @@ mod tests { #[test] fn test_parse_port_forward_config_8() { assert_eq!( - PortForwardConfig::from_notation("localhost:8080:localhost:8081:TCP") - .expect("Failed to parse"), + PortForwardConfig::from_notation( + "localhost:8080:localhost:8081:TCP", + DEFAULT_PORT_FORWARD_SOURCE + ) + .expect("Failed to parse"), vec![PortForwardConfig { source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(), destination: "localhost:8081".to_socket_addrs().unwrap().next().unwrap(), - protocol: PortProtocol::Tcp + protocol: PortProtocol::Tcp, + remote: false, }] ); } From 5e94a0f31e9ec6421593c179674fc196b05ad146 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Wed, 22 Jun 2022 23:06:16 -0600 Subject: [PATCH 075/165] Add host address binding option --- src/config.rs | 13 +++++++++++++ src/wg.rs | 14 +++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index eb48510..3292673 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,6 +19,7 @@ pub struct Config { pub(crate) private_key: Arc, pub(crate) endpoint_public_key: Arc, pub(crate) endpoint_addr: SocketAddr, + pub(crate) host_addr: Option, pub(crate) source_peer_ip: IpAddr, pub(crate) keepalive_seconds: Option, pub(crate) max_transmission_unit: usize, @@ -76,6 +77,12 @@ impl Config { .long("endpoint-addr") .env("ONETUN_ENDPOINT_ADDR") .help("The address (IP + port) of the WireGuard endpoint (remote). Example: 1.2.3.4:51820"), + Arg::with_name("host-addr") + .required(false) + .takes_value(true) + .long("host-addr") + .env("ONETUN_HOST_ADDR") + .help("The address (IP + port) for the tunnel to bind to. Example: 1.2.4:51820"), Arg::with_name("source-peer-ip") .required(true) .takes_value(true) @@ -237,6 +244,12 @@ impl Config { ), endpoint_addr: parse_addr(matches.value_of("endpoint-addr")) .with_context(|| "Invalid endpoint address")?, + host_addr: match matches.value_of("host-addr") { + Some(host_addr) => { + Some(parse_addr(Some(host_addr)).with_context(|| "Invalid host address")?) + } + None => None, + }, source_peer_ip, keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) .with_context(|| "Invalid keep-alive value")?, diff --git a/src/wg.rs b/src/wg.rs index 1d06444..a55fd99 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -36,11 +36,15 @@ impl WireGuardTunnel { let source_peer_ip = config.source_peer_ip; let peer = Self::create_tunnel(config)?; let endpoint = config.endpoint_addr; - let udp = UdpSocket::bind(match endpoint { - SocketAddr::V4(_) => "0.0.0.0:0", - SocketAddr::V6(_) => "[::]:0", - }) - .await + let udp = if let Some(host) = config.host_addr { + UdpSocket::bind(host).await + } else { + UdpSocket::bind(match endpoint { + SocketAddr::V4(_) => "0.0.0.0:0", + SocketAddr::V6(_) => "[::]:0", + }) + .await + } .with_context(|| "Failed to create UDP socket for WireGuard connection")?; Ok(Self { From b108b5f404ff0f8024a3ade3a5182e1f97f36a11 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 23 Jun 2022 22:47:33 -0600 Subject: [PATCH 076/165] Clarify help instructions for host binding --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 3292673..5629b02 100644 --- a/src/config.rs +++ b/src/config.rs @@ -82,7 +82,7 @@ impl Config { .takes_value(true) .long("host-addr") .env("ONETUN_HOST_ADDR") - .help("The address (IP + port) for the tunnel to bind to. Example: 1.2.4:51820"), + .help("The address (IP + port) used to bind the local UDP socket for the WireGuard tunnel. Example: 1.2.3.4:30000. Defaults to 0.0.0.0:0 for IPv4 endpoints, or [::]:0 for IPv6 endpoints."), Arg::with_name("source-peer-ip") .required(true) .takes_value(true) From c8a62debb16ab0685119e80ce1aee802eb71e863 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Fri, 24 Jun 2022 00:57:28 -0400 Subject: [PATCH 077/165] Simplify README intro --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f7fe03a..f79cc04 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,13 @@ A cross-platform, user-space WireGuard port-forwarder that requires no system ne ## Use-case -- You have an existing WireGuard endpoint (router), accessible using its UDP endpoint (typically port 51820); and -- You have a peer on the WireGuard network, running a TCP or UDP service on a port accessible to the WireGuard network; and -- You want to access this TCP or UDP service from a second computer, on which you can't install WireGuard because you - can't (no root access) or don't want to (polluting OS configs). +Access TCP or UDP services running on your WireGuard network, from devices that don't have WireGuard installed. -For example, this can be useful to access a port on your WireGuard network from a dev machine that doesn't have WireGuard installed. +Examples: + +- Personal or shared computers where you can't install WireGuard (root) +- IoT and mobile devices +- Root-less containers ## Download @@ -196,7 +197,7 @@ created in a [smoltcp](https://github.com/smoltcp-rs/smoltcp) "virtual" TCP/IP i process. An ephemeral "virtual port" is assigned to the "virtual client", which maps back to the local client. When the real client opens the connection, the virtual client socket opens a TCP connection to the virtual server -(a dummy socket bound to the remote host/port). The virtual interface in turn crafts the `SYN` segment and wraps it in an IP packet. +(a dummy socket bound to the remote host/port). The virtual interface in turn crafts the `SYN` segment and wraps it in an IP packet. Because of how the virtual client and server are configured, the IP packet is crafted with a source address being the configured `source-peer-ip` (`192.168.4.3` in the example above), and the destination address matches the port-forward's configured destination (`192.168.4.2`). From 3ab108ad041e3fb95355ad4e485a00c3df7011ed Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 23 Jun 2022 22:59:19 -0600 Subject: [PATCH 078/165] Move host address resolution logic to config --- src/config.rs | 34 +++++++++++++++++++++++++--------- src/wg.rs | 11 ++--------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5629b02..499ea3b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,7 +19,7 @@ pub struct Config { pub(crate) private_key: Arc, pub(crate) endpoint_public_key: Arc, pub(crate) endpoint_addr: SocketAddr, - pub(crate) host_addr: Option, + pub(crate) host_addr: SocketAddr, pub(crate) source_peer_ip: IpAddr, pub(crate) keepalive_seconds: Option, pub(crate) max_transmission_unit: usize, @@ -232,6 +232,28 @@ impl Config { .with_context(|| "Missing private key") }?; + let endpoint_addr = parse_addr(matches.value_of("endpoint-addr")) + .with_context(|| "Invalid endpoint address")?; + + let host_addr = if let Some(addr) = matches.value_of("host-addr") { + let addr = parse_addr(Some(addr)).with_context(|| "Invalid host address")?; + // Make sure the host address and endpoint address are the same IP version + if addr.ip().is_ipv6() && endpoint_addr.ip().is_ipv6() + || (addr.ip().is_ipv4() && endpoint_addr.ip().is_ipv4()) + { + return Err(anyhow::anyhow!( + "Host address and endpoint address must be the same IP version" + )); + } + addr + } else { + // Return the IP version of the endpoint address + match endpoint_addr { + SocketAddr::V4(_) => parse_addr(Some("0.0.0.0:0"))?, + SocketAddr::V6(_) => parse_addr(Some("[::]:0"))?, + } + }; + Ok(Self { port_forwards, remote_port_forwards, @@ -242,14 +264,8 @@ impl Config { parse_public_key(matches.value_of("endpoint-public-key")) .with_context(|| "Invalid endpoint public key")?, ), - endpoint_addr: parse_addr(matches.value_of("endpoint-addr")) - .with_context(|| "Invalid endpoint address")?, - host_addr: match matches.value_of("host-addr") { - Some(host_addr) => { - Some(parse_addr(Some(host_addr)).with_context(|| "Invalid host address")?) - } - None => None, - }, + endpoint_addr, + host_addr, source_peer_ip, keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) .with_context(|| "Invalid keep-alive value")?, diff --git a/src/wg.rs b/src/wg.rs index a55fd99..3ee8ca7 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -36,16 +36,9 @@ impl WireGuardTunnel { let source_peer_ip = config.source_peer_ip; let peer = Self::create_tunnel(config)?; let endpoint = config.endpoint_addr; - let udp = if let Some(host) = config.host_addr { - UdpSocket::bind(host).await - } else { - UdpSocket::bind(match endpoint { - SocketAddr::V4(_) => "0.0.0.0:0", - SocketAddr::V6(_) => "[::]:0", - }) + let udp = UdpSocket::bind(config.host_addr) .await - } - .with_context(|| "Failed to create UDP socket for WireGuard connection")?; + .with_context(|| "Failed to create UDP socket for WireGuard connection")?; Ok(Self { source_peer_ip, From 14df68ecc95091fbc4c20d68cb38849cf9879b52 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Fri, 24 Jun 2022 01:01:26 -0400 Subject: [PATCH 079/165] Simplify README intro --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f79cc04..9bdf559 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # onetun -A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations. +A cross-platform, user-space WireGuard port-forwarder that requires **no root-access**, and **no system network configurations**. [![crates.io](https://img.shields.io/crates/v/onetun.svg)](https://crates.io/crates/onetun) [![MIT licensed](https://img.shields.io/crates/l/onetun.svg)](./LICENSE) @@ -13,7 +13,7 @@ A cross-platform, user-space WireGuard port-forwarder that requires no system ne Access TCP or UDP services running on your WireGuard network, from devices that don't have WireGuard installed. -Examples: +For example, - Personal or shared computers where you can't install WireGuard (root) - IoT and mobile devices From c647bc9a96b51e6e44998dfcc5dd376a0342c26f Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 23 Jun 2022 23:01:32 -0600 Subject: [PATCH 080/165] Rename host_addr to endpoint_bind_addr --- src/config.rs | 12 ++++++------ src/wg.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index 499ea3b..490d90c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,7 +19,7 @@ pub struct Config { pub(crate) private_key: Arc, pub(crate) endpoint_public_key: Arc, pub(crate) endpoint_addr: SocketAddr, - pub(crate) host_addr: SocketAddr, + pub(crate) endpoint_bind_addr: SocketAddr, pub(crate) source_peer_ip: IpAddr, pub(crate) keepalive_seconds: Option, pub(crate) max_transmission_unit: usize, @@ -77,11 +77,11 @@ impl Config { .long("endpoint-addr") .env("ONETUN_ENDPOINT_ADDR") .help("The address (IP + port) of the WireGuard endpoint (remote). Example: 1.2.3.4:51820"), - Arg::with_name("host-addr") + Arg::with_name("endpoint-bind-addr") .required(false) .takes_value(true) - .long("host-addr") - .env("ONETUN_HOST_ADDR") + .long("endpoint-bind-addr") + .env("ONETUN_ENDPOINT_BIND_ADDR") .help("The address (IP + port) used to bind the local UDP socket for the WireGuard tunnel. Example: 1.2.3.4:30000. Defaults to 0.0.0.0:0 for IPv4 endpoints, or [::]:0 for IPv6 endpoints."), Arg::with_name("source-peer-ip") .required(true) @@ -235,7 +235,7 @@ impl Config { let endpoint_addr = parse_addr(matches.value_of("endpoint-addr")) .with_context(|| "Invalid endpoint address")?; - let host_addr = if let Some(addr) = matches.value_of("host-addr") { + let endpoint_bind_addr = if let Some(addr) = matches.value_of("endpoint-bind-addr") { let addr = parse_addr(Some(addr)).with_context(|| "Invalid host address")?; // Make sure the host address and endpoint address are the same IP version if addr.ip().is_ipv6() && endpoint_addr.ip().is_ipv6() @@ -265,7 +265,7 @@ impl Config { .with_context(|| "Invalid endpoint public key")?, ), endpoint_addr, - host_addr, + endpoint_bind_addr, source_peer_ip, keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) .with_context(|| "Invalid keep-alive value")?, diff --git a/src/wg.rs b/src/wg.rs index 3ee8ca7..2bacc22 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -36,7 +36,7 @@ impl WireGuardTunnel { let source_peer_ip = config.source_peer_ip; let peer = Self::create_tunnel(config)?; let endpoint = config.endpoint_addr; - let udp = UdpSocket::bind(config.host_addr) + let udp = UdpSocket::bind(config.endpoint_bind_addr) .await .with_context(|| "Failed to create UDP socket for WireGuard connection")?; From a81f5fe5e6e15605c55a658ac25720cc3c6c1649 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Fri, 24 Jun 2022 01:01:53 -0400 Subject: [PATCH 081/165] Simplify README intro --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9bdf559..9760ca6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # onetun -A cross-platform, user-space WireGuard port-forwarder that requires **no root-access**, and **no system network configurations**. +A cross-platform, user-space WireGuard port-forwarder that requires **no root-access or system network configurations**. [![crates.io](https://img.shields.io/crates/v/onetun.svg)](https://crates.io/crates/onetun) [![MIT licensed](https://img.shields.io/crates/l/onetun.svg)](./LICENSE) From 96e18edd190bcbd8072536d2e71fadab850a70e2 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 23 Jun 2022 23:10:11 -0600 Subject: [PATCH 082/165] Invert logic for IP version mismatch --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 490d90c..3a22e5d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -238,8 +238,8 @@ impl Config { let endpoint_bind_addr = if let Some(addr) = matches.value_of("endpoint-bind-addr") { let addr = parse_addr(Some(addr)).with_context(|| "Invalid host address")?; // Make sure the host address and endpoint address are the same IP version - if addr.ip().is_ipv6() && endpoint_addr.ip().is_ipv6() - || (addr.ip().is_ipv4() && endpoint_addr.ip().is_ipv4()) + if addr.ip().is_ipv6() != endpoint_addr.ip().is_ipv6() + || (addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4()) { return Err(anyhow::anyhow!( "Host address and endpoint address must be the same IP version" From 1680b17c4715d4153a60f7f8ce678a5c1d5c67ae Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 23 Jun 2022 23:10:48 -0600 Subject: [PATCH 083/165] Correct binding terminoligy IP version detection --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 3a22e5d..baf8b1d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -242,7 +242,7 @@ impl Config { || (addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4()) { return Err(anyhow::anyhow!( - "Host address and endpoint address must be the same IP version" + "Endpoint and bind addresses must be the same IP version" )); } addr From 9bd7ec2cca784cb0ebc46763c91d9a71f446cae7 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 23 Jun 2022 23:11:45 -0600 Subject: [PATCH 084/165] Simplify IP version detection --- src/config.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index baf8b1d..6b86428 100644 --- a/src/config.rs +++ b/src/config.rs @@ -238,9 +238,7 @@ impl Config { let endpoint_bind_addr = if let Some(addr) = matches.value_of("endpoint-bind-addr") { let addr = parse_addr(Some(addr)).with_context(|| "Invalid host address")?; // Make sure the host address and endpoint address are the same IP version - if addr.ip().is_ipv6() != endpoint_addr.ip().is_ipv6() - || (addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4()) - { + if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() { return Err(anyhow::anyhow!( "Endpoint and bind addresses must be the same IP version" )); From 4162f62ae67fde6887e8fdd375c8b650bbdae8f6 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Thu, 23 Jun 2022 23:14:57 -0600 Subject: [PATCH 085/165] Change the error message from host to bind --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 6b86428..ccae551 100644 --- a/src/config.rs +++ b/src/config.rs @@ -236,8 +236,8 @@ impl Config { .with_context(|| "Invalid endpoint address")?; let endpoint_bind_addr = if let Some(addr) = matches.value_of("endpoint-bind-addr") { - let addr = parse_addr(Some(addr)).with_context(|| "Invalid host address")?; - // Make sure the host address and endpoint address are the same IP version + let addr = parse_addr(Some(addr)).with_context(|| "Invalid bind address")?; + // Make sure the bind address and endpoint address are the same IP version if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() { return Err(anyhow::anyhow!( "Endpoint and bind addresses must be the same IP version" From f85692950f9df3f3fccd3f3e69da1a5918739cc9 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Fri, 24 Jun 2022 01:45:31 -0400 Subject: [PATCH 086/165] Split dependencies that are only used for the binary version of onetun --- Cargo.toml | 14 +++++++++++--- src/config.rs | 4 +++- src/main.rs | 3 +++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aeeb8fc..929cdcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,15 +10,23 @@ repository = "https://github.com/aramperes/onetun" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +# Required dependencies (bin and lib) boringtun = { version = "0.4.0", default-features = false } -clap = { version = "2.33", default-features = false, features = ["suggestions"] } log = "0.4" -pretty_env_logger = "0.4" anyhow = "1" -smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } tokio = { version = "1", features = ["full"] } futures = "0.3.17" rand = "0.8.4" nom = "7" async-trait = "0.1.51" priority-queue = "1.2.0" +smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } + +# bin-only dependencies +clap = { version = "2.33", default-features = false, features = ["suggestions"], optional = true } +pretty_env_logger = { version = "0.4", optional = true } + +[features] +pcap = [] +default = [ "bin" ] +bin = [ "clap", "pretty_env_logger", "pcap" ] diff --git a/src/config.rs b/src/config.rs index ccae551..e055466 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,6 @@ use std::sync::Arc; use anyhow::Context; use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; -use clap::{App, Arg}; const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; @@ -29,7 +28,10 @@ pub struct Config { } impl Config { + #[cfg(feature = "bin")] pub fn from_args() -> anyhow::Result { + use clap::{App, Arg}; + let mut warnings = vec![]; let matches = App::new("onetun") diff --git a/src/main.rs b/src/main.rs index 879b45e..c9ad9ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,12 +17,14 @@ use crate::wg::WireGuardTunnel; pub mod config; pub mod events; +#[cfg(feature = "pcap")] pub mod pcap; pub mod tunnel; pub mod virtual_device; pub mod virtual_iface; pub mod wg; +#[cfg(feature = "bin")] #[tokio::main] async fn main() -> anyhow::Result<()> { let config = Config::from_args().with_context(|| "Failed to read config")?; @@ -126,6 +128,7 @@ async fn main() -> anyhow::Result<()> { futures::future::pending().await } +#[cfg(feature = "bin")] fn init_logger(config: &Config) -> anyhow::Result<()> { let mut builder = pretty_env_logger::formatted_timed_builder(); builder.parse_filters(&config.log); From 48eaf0f840b5a44b04af4eae4074d56fdcb34e85 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 10:33:37 -0400 Subject: [PATCH 087/165] Allow onetun to be used as a library --- .github/workflows/build.yml | 5 ++ Cargo.toml | 2 + src/config.rs | 24 +++---- src/lib.rs | 122 ++++++++++++++++++++++++++++++++++ src/main.rs | 126 ++++-------------------------------- 5 files changed, 154 insertions(+), 125 deletions(-) create mode 100644 src/lib.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb404a0..01fafed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,11 @@ jobs: with: command: check + - name: Run cargo check without default features + uses: actions-rs/cargo@v1 + with: + command: check --no-default-features + test: name: Test Suite runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 929cdcf..55d9eea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,5 @@ pretty_env_logger = { version = "0.4", optional = true } pcap = [] default = [ "bin" ] bin = [ "clap", "pretty_env_logger", "pcap" ] + +[lib] diff --git a/src/config.rs b/src/config.rs index e055466..20f3093 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,19 +12,19 @@ const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; #[derive(Clone, Debug)] pub struct Config { - pub(crate) port_forwards: Vec, + pub port_forwards: Vec, #[allow(dead_code)] - pub(crate) remote_port_forwards: Vec, - pub(crate) private_key: Arc, - pub(crate) endpoint_public_key: Arc, - pub(crate) endpoint_addr: SocketAddr, - pub(crate) endpoint_bind_addr: SocketAddr, - pub(crate) source_peer_ip: IpAddr, - pub(crate) keepalive_seconds: Option, - pub(crate) max_transmission_unit: usize, - pub(crate) log: String, - pub(crate) warnings: Vec, - pub(crate) pcap_file: Option, + pub remote_port_forwards: Vec, + pub private_key: Arc, + pub endpoint_public_key: Arc, + pub endpoint_addr: SocketAddr, + pub endpoint_bind_addr: SocketAddr, + pub source_peer_ip: IpAddr, + pub keepalive_seconds: Option, + pub max_transmission_unit: usize, + pub log: String, + pub warnings: Vec, + pub pcap_file: Option, } impl Config { diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..57a3fc4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,122 @@ +#[macro_use] +extern crate log; + +use std::sync::Arc; + +use anyhow::Context; + +use crate::config::{Config, PortProtocol}; +use crate::events::Bus; +use crate::tunnel::tcp::TcpPortPool; +use crate::tunnel::udp::UdpPortPool; +use crate::virtual_device::VirtualIpDevice; +use crate::virtual_iface::tcp::TcpVirtualInterface; +use crate::virtual_iface::udp::UdpVirtualInterface; +use crate::virtual_iface::VirtualInterfacePoll; +use crate::wg::WireGuardTunnel; + +pub mod config; +pub mod events; +#[cfg(feature = "pcap")] +pub mod pcap; +pub mod tunnel; +pub mod virtual_device; +pub mod virtual_iface; +pub mod wg; + +/// Starts the onetun tunnels in separate tokio tasks. +/// +/// Note: This future completes immediately. +pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> { + // Initialize the port pool for each protocol + let tcp_port_pool = TcpPortPool::new(); + let udp_port_pool = UdpPortPool::new(); + + #[cfg(feature = "pcap")] + if let Some(pcap_file) = config.pcap_file.clone() { + // Start packet capture + let bus = bus.clone(); + tokio::spawn(async move { pcap::capture(pcap_file, bus).await }); + } + + let wg = WireGuardTunnel::new(&config, bus.clone()) + .await + .with_context(|| "Failed to initialize WireGuard tunnel")?; + let wg = Arc::new(wg); + + { + // Start routine task for WireGuard + let wg = wg.clone(); + tokio::spawn(async move { wg.routine_task().await }); + } + + { + // Start consumption task for WireGuard + let wg = wg.clone(); + tokio::spawn(async move { wg.consume_task().await }); + } + + { + // Start production task for WireGuard + let wg = wg.clone(); + tokio::spawn(async move { wg.produce_task().await }); + } + + if config + .port_forwards + .iter() + .any(|pf| pf.protocol == PortProtocol::Tcp) + { + // TCP device + let bus = bus.clone(); + let device = + VirtualIpDevice::new(PortProtocol::Tcp, bus.clone(), config.max_transmission_unit); + + // Start TCP Virtual Interface + let port_forwards = config.port_forwards.clone(); + let iface = TcpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); + tokio::spawn(async move { iface.poll_loop(device).await }); + } + + if config + .port_forwards + .iter() + .any(|pf| pf.protocol == PortProtocol::Udp) + { + // UDP device + let bus = bus.clone(); + let device = + VirtualIpDevice::new(PortProtocol::Udp, bus.clone(), config.max_transmission_unit); + + // Start UDP Virtual Interface + let port_forwards = config.port_forwards.clone(); + let iface = UdpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); + tokio::spawn(async move { iface.poll_loop(device).await }); + } + + { + let port_forwards = config.port_forwards; + let source_peer_ip = config.source_peer_ip; + + port_forwards + .into_iter() + .map(|pf| { + ( + pf, + wg.clone(), + tcp_port_pool.clone(), + udp_port_pool.clone(), + bus.clone(), + ) + }) + .for_each(move |(pf, wg, tcp_port_pool, udp_port_pool, bus)| { + tokio::spawn(async move { + tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg, bus) + .await + .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) + }); + }); + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index c9ad9ce..78c6419 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,13 @@ +#[cfg(feature = "bin")] #[macro_use] extern crate log; -use std::sync::Arc; - -use anyhow::Context; - -use crate::config::{Config, PortProtocol}; -use crate::events::Bus; -use crate::tunnel::tcp::TcpPortPool; -use crate::tunnel::udp::UdpPortPool; -use crate::virtual_device::VirtualIpDevice; -use crate::virtual_iface::tcp::TcpVirtualInterface; -use crate::virtual_iface::udp::UdpVirtualInterface; -use crate::virtual_iface::VirtualInterfacePoll; -use crate::wg::WireGuardTunnel; - -pub mod config; -pub mod events; -#[cfg(feature = "pcap")] -pub mod pcap; -pub mod tunnel; -pub mod virtual_device; -pub mod virtual_iface; -pub mod wg; - #[cfg(feature = "bin")] #[tokio::main] async fn main() -> anyhow::Result<()> { + use anyhow::Context; + use onetun::{config::Config, events::Bus}; + let config = Config::from_args().with_context(|| "Failed to read config")?; init_logger(&config)?; @@ -34,102 +15,21 @@ async fn main() -> anyhow::Result<()> { warn!("{}", warning); } - // Initialize the port pool for each protocol - let tcp_port_pool = TcpPortPool::new(); - let udp_port_pool = UdpPortPool::new(); - let bus = Bus::default(); - - if let Some(pcap_file) = config.pcap_file.clone() { - // Start packet capture - let bus = bus.clone(); - tokio::spawn(async move { pcap::capture(pcap_file, bus).await }); - } - - let wg = WireGuardTunnel::new(&config, bus.clone()) - .await - .with_context(|| "Failed to initialize WireGuard tunnel")?; - let wg = Arc::new(wg); - - { - // Start routine task for WireGuard - let wg = wg.clone(); - tokio::spawn(async move { wg.routine_task().await }); - } - - { - // Start consumption task for WireGuard - let wg = wg.clone(); - tokio::spawn(async move { wg.consume_task().await }); - } - - { - // Start production task for WireGuard - let wg = wg.clone(); - tokio::spawn(async move { wg.produce_task().await }); - } - - if config - .port_forwards - .iter() - .any(|pf| pf.protocol == PortProtocol::Tcp) - { - // TCP device - let bus = bus.clone(); - let device = - VirtualIpDevice::new(PortProtocol::Tcp, bus.clone(), config.max_transmission_unit); - - // Start TCP Virtual Interface - let port_forwards = config.port_forwards.clone(); - let iface = TcpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); - tokio::spawn(async move { iface.poll_loop(device).await }); - } - - if config - .port_forwards - .iter() - .any(|pf| pf.protocol == PortProtocol::Udp) - { - // UDP device - let bus = bus.clone(); - let device = - VirtualIpDevice::new(PortProtocol::Udp, bus.clone(), config.max_transmission_unit); - - // Start UDP Virtual Interface - let port_forwards = config.port_forwards.clone(); - let iface = UdpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); - tokio::spawn(async move { iface.poll_loop(device).await }); - } - - { - let port_forwards = config.port_forwards; - let source_peer_ip = config.source_peer_ip; - - port_forwards - .into_iter() - .map(|pf| { - ( - pf, - wg.clone(), - tcp_port_pool.clone(), - udp_port_pool.clone(), - bus.clone(), - ) - }) - .for_each(move |(pf, wg, tcp_port_pool, udp_port_pool, bus)| { - tokio::spawn(async move { - tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg, bus) - .await - .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) - }); - }); - } + onetun::start_tunnels(config, bus).await?; futures::future::pending().await } +#[cfg(not(feature = "bin"))] +fn main() -> anyhow::Result<()> { + Err(anyhow::anyhow!("Binary compiled without 'bin' feature")) +} + #[cfg(feature = "bin")] -fn init_logger(config: &Config) -> anyhow::Result<()> { +fn init_logger(config: &onetun::config::Config) -> anyhow::Result<()> { + use anyhow::Context; + let mut builder = pretty_env_logger::formatted_timed_builder(); builder.parse_filters(&config.log); builder From 1a560434d4028edce9998970f5dd3d75221dcc99 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 10:39:44 -0400 Subject: [PATCH 088/165] Fix cargo check action --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01fafed..a1c4af2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,8 @@ jobs: - name: Run cargo check without default features uses: actions-rs/cargo@v1 with: - command: check --no-default-features + command: check + args: --no-default-features test: name: Test Suite From 8c1bdb1700a7aaa2319baa64abb8c104ed868893 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 10:49:37 -0400 Subject: [PATCH 089/165] Minimize tokio features --- Cargo.lock | 50 ++------------------------------------------------ Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b148531..e4b6ecd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,7 +69,7 @@ dependencies = [ "ip_network_table", "jni", "libc", - "parking_lot 0.12.0", + "parking_lot", "ring", "tracing", "untrusted 0.9.0", @@ -286,15 +286,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "ip_network" version = "0.4.1" @@ -470,17 +461,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", -] - [[package]] name = "parking_lot" version = "0.12.0" @@ -488,21 +468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -676,15 +642,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - [[package]] name = "slab" version = "0.4.5" @@ -782,10 +739,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", - "parking_lot 0.11.2", "pin-project-lite", - "signal-hook-registry", "tokio-macros", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index 55d9eea..f41c2b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/aramperes/onetun" boringtun = { version = "0.4.0", default-features = false } log = "0.4" anyhow = "1" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = [ "rt", "sync", "io-util", "net", "time", "fs", "macros" ] } futures = "0.3.17" rand = "0.8.4" nom = "7" @@ -29,6 +29,6 @@ pretty_env_logger = { version = "0.4", optional = true } [features] pcap = [] default = [ "bin" ] -bin = [ "clap", "pretty_env_logger", "pcap" ] +bin = [ "clap", "pretty_env_logger", "pcap", "tokio/rt-multi-thread" ] [lib] From 75bad318f44db09c12e8a013c4ac9478d3704f0a Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 10:54:47 -0400 Subject: [PATCH 090/165] release: v0.3.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4b6ecd..47760d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,7 +445,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index f41c2b5..14adbc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.0" +version = "0.3.1" edition = "2018" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From 00b45f8cb40d04790d817560d95522e1027bbac7 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 11:12:23 -0400 Subject: [PATCH 091/165] Update to Edition 2021 and fix docker build --- .github/workflows/build.yml | 4 ++-- Cargo.toml | 2 +- Dockerfile | 3 ++- README.md | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1c4af2..6ff5fdc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.55.0 + - 1.56.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.55.0 + - 1.56.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 14adbc5..6eca0d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "onetun" version = "0.3.1" -edition = "2018" +edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." authors = ["Aram Peres "] diff --git a/Dockerfile b/Dockerfile index 9edc22a..475bfc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,11 @@ -FROM rust:1.55 as cargo-build +FROM rust:1.56 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml # Placeholder to download dependencies and cache them using layering RUN mkdir src/ +RUN touch src/lib.rs RUN echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs RUN cargo build --release RUN rm -f target/x86_64-unknown-linux-musl/release/deps/myapp* diff --git a/README.md b/README.md index 9760ca6..1b06356 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.55: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.56: ```shell $ cargo install onetun @@ -37,7 +37,7 @@ $ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.55: +You can also build onetun locally, using Rust ≥1.56: ```shell $ git clone https://github.com/aramperes/onetun && cd onetun From 371a55bb7165f55c3b8031d29a9a371e86808a64 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 11:15:16 -0400 Subject: [PATCH 092/165] release: 0.3.2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47760d5..5da7a73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,7 +445,7 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onetun" -version = "0.3.1" +version = "0.3.2" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 6eca0d0..33b19f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.1" +version = "0.3.2" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From c09a541788cd2b0b3b2d01703f324106d0d21d01 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 13:55:26 -0400 Subject: [PATCH 093/165] Update dependencies --- Cargo.lock | 324 +++++++++++++++++++++++++---------------------------- 1 file changed, 150 insertions(+), 174 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5da7a73..1a46264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,15 +13,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.51" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ "proc-macro2", "quote", @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byteorder" @@ -95,9 +95,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cesu8" @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.3" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" dependencies = [ "bytes", "memchr", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -163,9 +163,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -173,15 +173,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -190,15 +190,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", @@ -207,21 +207,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-channel", "futures-core", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" [[package]] name = "hermit-abi" @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown", @@ -330,9 +330,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" dependencies = [ "wasm-bindgen", ] @@ -345,24 +345,25 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -375,9 +376,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" @@ -387,51 +388,31 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.7.14" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi", + "windows-sys", ] [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", -] - -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -439,9 +420,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "onetun" @@ -463,9 +444,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -473,9 +454,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", @@ -486,9 +467,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -498,9 +479,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty_env_logger" @@ -514,9 +495,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00ba480ac08d3cfc40dea10fd466fd2c14dee3ea6fc7873bc4079eda2727caf0" +checksum = "de9cde7493f5f5d2d163b174be9f9a72d756b79b0f6ed85654128d238c347c1e" dependencies = [ "autocfg", "indexmap", @@ -524,11 +505,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.34" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -539,23 +520,22 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.10" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -577,29 +557,20 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -608,9 +579,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "ring" @@ -644,27 +615,36 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2" [[package]] name = "smoltcp" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2308a1657c8db1f5b4993bab4e620bdbe5623bd81f254cf60326767bb243237" +checksum = "72165c4af59f5f19c7fb774b88b95660591b612380305b5f4503157341a9f7ee" dependencies = [ "bitflags", "byteorder", "log", "managed", - "rand_core", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", ] [[package]] @@ -681,20 +661,20 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.82" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] @@ -710,18 +690,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -730,25 +710,27 @@ dependencies = [ [[package]] name = "tokio" -version = "1.15.0" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" dependencies = [ "bytes", "libc", "memchr", "mio", "num_cpus", + "once_cell", "pin-project-lite", + "socket2", "tokio-macros", "winapi", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -757,9 +739,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.30" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if", "pin-project-lite", @@ -769,9 +751,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -780,25 +762,25 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static", + "once_cell", ] +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + [[package]] name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - [[package]] name = "untrusted" version = "0.7.1" @@ -811,12 +793,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - [[package]] name = "walkdir" version = "2.3.2" @@ -830,15 +806,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -846,9 +822,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static", @@ -861,9 +837,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -871,9 +847,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -884,15 +860,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" dependencies = [ "js-sys", "wasm-bindgen", @@ -931,9 +907,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", @@ -944,30 +920,30 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" From 96be4214956f52e05dc11d1f01127bfd314ee2ca Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 14:03:18 -0400 Subject: [PATCH 094/165] Increase MSRV to 1.56.1 --- .github/workflows/build.yml | 4 ++-- Dockerfile | 2 +- README.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ff5fdc..8280928 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.56.0 + - 1.56.1 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.56.0 + - 1.56.1 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/Dockerfile b/Dockerfile index 475bfc9..c297540 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.56 as cargo-build +FROM rust:1.56.1 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml diff --git a/README.md b/README.md index 1b06356..17eef9f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.56: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.56.1: ```shell $ cargo install onetun @@ -37,7 +37,7 @@ $ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.56: +You can also build onetun locally, using Rust ≥1.56.1: ```shell $ git clone https://github.com/aramperes/onetun && cd onetun From 8cee210ccb569509bef97bfc1f4e7a9697944f38 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 14:38:08 -0400 Subject: [PATCH 095/165] Expose boringtun x25519 primitives --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 20f3093..9d9732d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::sync::Arc; use anyhow::Context; -use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; +pub use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; From b78cab58ee6599bc4306462a92c43de0d23ed54f Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 25 Jun 2022 15:05:10 -0400 Subject: [PATCH 096/165] release: v0.3.3 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a46264..d8ea4ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -426,7 +426,7 @@ checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "onetun" -version = "0.3.2" +version = "0.3.3" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 33b19f5..59ce18f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.2" +version = "0.3.3" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From aef90a5c0c52faf70658c3c3d09297fbea6ca7bc Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Mon, 18 Jul 2022 20:41:48 -0400 Subject: [PATCH 097/165] Add --endpoint-bind-addr to README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 17eef9f..439b49b 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,16 @@ To capture packets sent to and from the onetun local port, you must use an exter $ sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8080' ``` +### WireGuard Options + +By default, onetun will create the UDP socket to communicate with the WireGuard endpoint on all interfaces and on a dynamic port, +i.e. `0.0.0.0:0` for IPv4 endpoints, or `[::]:0` for IPv6. +You can bind to a static address instead using `--endpoint-bind-addr`: + +``` +$ onetun --endpoint-bind-addr 0.0.0.0:51820 --endpoint-addr 140.30.3.182:51820 [...] +``` + ## Architecture **In short:** onetun uses [smoltcp's](https://github.com/smoltcp-rs/smoltcp) TCP/IP and UDP stack to generate IP packets From cea343c2c9391716957f53ea803663454e4adf29 Mon Sep 17 00:00:00 2001 From: Sam Hug Date: Thu, 11 Aug 2022 15:32:24 -0700 Subject: [PATCH 098/165] heap alloc WireGuardTunnel::consume_task() future --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 57a3fc4..a43d657 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> { { // Start consumption task for WireGuard let wg = wg.clone(); - tokio::spawn(async move { wg.consume_task().await }); + tokio::spawn(Box::pin(async move { wg.consume_task().await })); } { From 074e1b430c4d605047d653d256bc029a49e3872b Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Fri, 12 Aug 2022 18:44:18 +0800 Subject: [PATCH 099/165] Fix typos and markdowns Found via these commands: codespell -L crate markdownlint -f README.md --disable MD013 MD033 MD041 --- README.md | 34 +++++++++++++++++----------------- src/tunnel/tcp.rs | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 439b49b..2f3c4bf 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ For example, onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.56.1: ```shell -$ cargo install onetun +cargo install onetun ``` You can also download the binary for Windows, macOS (Intel), and Linux (amd64) from @@ -40,9 +40,9 @@ $ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ You can also build onetun locally, using Rust ≥1.56.1: ```shell -$ git clone https://github.com/aramperes/onetun && cd onetun -$ cargo build --release -$ ./target/release/onetun +git clone https://github.com/aramperes/onetun && cd onetun +cargo build --release +./target/release/onetun ``` ## Usage @@ -54,7 +54,7 @@ access, or install any WireGuard tool on your local system for it to work. The only prerequisite is to register a peer IP and public key on the remote WireGuard endpoint; those are necessary for the WireGuard endpoint to trust the onetun peer and for packets to be routed. -``` +```shell onetun [src_host:]::[:TCP,UDP,...] [...] \ --endpoint-addr \ --endpoint-public-key \ @@ -70,7 +70,7 @@ onetun [src_host:]::[:TCP,UDP,...] [...] \ Suppose your WireGuard endpoint has the following configuration, and is accessible from `140.30.3.182:51820`: -``` +```shell # /etc/wireguard/wg0.conf [Interface] @@ -103,13 +103,13 @@ onetun 127.0.0.1:8080:192.168.4.2:8080 \ You'll then see this log: -``` +```shell INFO onetun > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` Which means you can now access the port locally! -``` +```shell $ curl 127.0.0.1:8080 Hello world! ``` @@ -118,7 +118,7 @@ Hello world! **onetun** supports running multiple tunnels in parallel. For example: -``` +```shell $ onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081 INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [140.30.3.182:51820] as peer 192.168.4.3) @@ -131,7 +131,7 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [ **onetun** supports UDP forwarding. You can add `:UDP` at the end of the port-forward configuration, or `UDP,TCP` to support both protocols on the same port (note that this opens 2 separate tunnels, just on the same port) -``` +```shell $ onetun 127.0.0.1:8080:192.168.4.2:8080:UDP INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) @@ -147,7 +147,7 @@ it in any production capacity. **onetun** supports both IPv4 and IPv6. In fact, you can use onetun to forward some IP version to another, e.g. 6-to-4: -``` +```shell $ onetun [::1]:8080:192.168.4.2:8080 INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -155,7 +155,7 @@ INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140. Note that each tunnel can only support one "source" IP version and one "destination" IP version. If you want to support both IPv4 and IPv6 on the same port, you should create a second port-forward: -``` +```shell $ onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080 INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) @@ -166,7 +166,7 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [ For debugging purposes, you can enable the capture of IP packets sent between onetun and the WireGuard peer. The output is a libpcap capture file that can be viewed with Wireshark. -``` +```shell $ onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080 INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) @@ -174,8 +174,8 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [ To capture packets sent to and from the onetun local port, you must use an external tool like `tcpdump` with root access: -``` -$ sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8080' +```shell +sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8080' ``` ### WireGuard Options @@ -184,8 +184,8 @@ By default, onetun will create the UDP socket to communicate with the WireGuard i.e. `0.0.0.0:0` for IPv4 endpoints, or `[::]:0` for IPv6. You can bind to a static address instead using `--endpoint-bind-addr`: -``` -$ onetun --endpoint-bind-addr 0.0.0.0:51820 --endpoint-addr 140.30.3.182:51820 [...] +```shell +onetun --endpoint-bind-addr 0.0.0.0:51820 --endpoint-addr 140.30.3.182:51820 [...] ``` ## Architecture diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index e9644b9..557f5ad 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -185,7 +185,7 @@ impl TcpPortPool { } } - /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). + /// Requests a free port from the pool. An error is returned if none is available (exhausted max capacity). pub async fn next(&self) -> anyhow::Result { let mut inner = self.inner.write().await; let port = inner From fbc76e3fb07a809f5ce80529a098aeea3c322756 Mon Sep 17 00:00:00 2001 From: Jovan Gerodetti Date: Sun, 25 Sep 2022 11:56:36 +0200 Subject: [PATCH 100/165] Handle WireGuardError::ConnectionExpired #44 --- Cargo.lock | 43 ++++++++++++++++++++---------- Cargo.toml | 3 +++ src/wg.rs | 76 +++++++++++++++++++++++++++++++++--------------------- 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8ea4ca..418c870 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.56" @@ -420,15 +431,16 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "onetun" version = "0.3.3" dependencies = [ "anyhow", + "async-recursion", "async-trait", "boringtun", "clap", @@ -440,6 +452,7 @@ dependencies = [ "rand", "smoltcp", "tokio", + "tracing", ] [[package]] @@ -690,18 +703,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" dependencies = [ "proc-macro2", "quote", @@ -710,10 +723,11 @@ dependencies = [ [[package]] name = "tokio" -version = "1.19.2" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" dependencies = [ + "autocfg", "bytes", "libc", "memchr", @@ -739,11 +753,12 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -751,9 +766,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -762,9 +777,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", ] diff --git a/Cargo.toml b/Cargo.toml index 59ce18f..c378155 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,13 @@ nom = "7" async-trait = "0.1.51" priority-queue = "1.2.0" smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } +# forward boringtuns tracing events to log +tracing = { version = "0.1.36", default-features = false, features = ["log"] } # bin-only dependencies clap = { version = "2.33", default-features = false, features = ["suggestions"], optional = true } pretty_env_logger = { version = "0.4", optional = true } +async-recursion = "1.0.0" [features] pcap = [] diff --git a/src/wg.rs b/src/wg.rs index 2bacc22..18ef7d5 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::Bus; use anyhow::Context; +use async_recursion::async_recursion; +use boringtun::noise::errors::WireGuardError; use boringtun::noise::{Tunn, TunnResult}; use log::Level; use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet}; @@ -102,39 +104,53 @@ impl WireGuardTunnel { loop { let mut send_buf = [0u8; MAX_PACKET]; - match self.peer.update_timers(&mut send_buf) { - TunnResult::WriteToNetwork(packet) => { - debug!( - "Sending routine packet of {} bytes to WireGuard endpoint", - packet.len() - ); - match self.udp.send_to(packet, self.endpoint).await { - Ok(_) => {} - Err(e) => { - error!( - "Failed to send routine packet to WireGuard endpoint: {:?}", - e - ); - } - }; - } - TunnResult::Err(e) => { - error!( - "Failed to prepare routine packet for WireGuard endpoint: {:?}", - e - ); - } - TunnResult::Done => { - // Sleep for a bit - tokio::time::sleep(Duration::from_millis(1)).await; - } - other => { - warn!("Unexpected WireGuard routine task state: {:?}", other); - } - } + let tun_result = self.peer.update_timers(&mut send_buf); + self.handle_routine_tun_result(tun_result).await; } } + #[async_recursion] + async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) -> () { + match result { + TunnResult::WriteToNetwork(packet) => { + debug!( + "Sending routine packet of {} bytes to WireGuard endpoint", + packet.len() + ); + match self.udp.send_to(packet, self.endpoint).await { + Ok(_) => {} + Err(e) => { + error!( + "Failed to send routine packet to WireGuard endpoint: {:?}", + e + ); + } + }; + } + TunnResult::Err(WireGuardError::ConnectionExpired) => { + warn!("Wireguard handshake has expired!"); + + let mut buf = vec![0u8; MAX_PACKET]; + let result = self.peer.format_handshake_initiation(&mut buf[..], false); + + self.handle_routine_tun_result(result).await + } + TunnResult::Err(e) => { + error!( + "Failed to prepare routine packet for WireGuard endpoint: {:?}", + e + ); + } + TunnResult::Done => { + // Sleep for a bit + tokio::time::sleep(Duration::from_millis(1)).await; + } + other => { + warn!("Unexpected WireGuard routine task state: {:?}", other); + } + }; + } + /// WireGuard consumption task. Receives encrypted packets from the WireGuard endpoint, /// decapsulates them, and dispatches newly received IP packets. pub async fn consume_task(&self) -> ! { From 6c645319401041eebc2415975049bde73f351e55 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 25 Sep 2022 17:19:16 -0400 Subject: [PATCH 101/165] chore: update dependencies --- Cargo.lock | 171 ++++++++++++++++++++++++++--------------------------- 1 file changed, 84 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 418c870..e15aeb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "async-recursion" @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byteorder" @@ -100,9 +100,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.4" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ "bytes", "memchr", @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -174,9 +174,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -184,15 +184,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -201,15 +201,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -218,21 +218,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-channel", "futures-core", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" @@ -341,30 +341,24 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" -version = "0.2.126" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -508,9 +502,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9cde7493f5f5d2d163b174be9f9a72d756b79b0f6ed85654128d238c347c1e" +checksum = "815082d99af3acc75a3e67efd2a07f72e67b4e81b4344eb8ca34c6ebf3dfa9c5" dependencies = [ "autocfg", "indexmap", @@ -518,9 +512,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" dependencies = [ "unicode-ident", ] @@ -533,9 +527,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -563,27 +557,27 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -592,9 +586,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "ring" @@ -628,15 +622,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smoltcp" @@ -652,9 +649,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -674,9 +671,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" dependencies = [ "proc-macro2", "quote", @@ -703,18 +700,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43" dependencies = [ "proc-macro2", "quote", @@ -786,15 +783,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "untrusted" @@ -827,9 +824,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -837,13 +834,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -852,9 +849,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -862,9 +859,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -875,15 +872,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", From 0553fce5c6bb34a4a00fcbc8c7f8de56ae99d375 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 25 Sep 2022 17:24:03 -0400 Subject: [PATCH 102/165] chore: bump msrv to 1.57 --- .github/workflows/build.yml | 4 ++-- Dockerfile | 2 +- README.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8280928..f931667 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.56.1 + - 1.57.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.56.1 + - 1.57.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/Dockerfile b/Dockerfile index c297540..280ebb5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.56.1 as cargo-build +FROM rust:1.57.0 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml diff --git a/README.md b/README.md index 2f3c4bf..eab9f07 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.56.1: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.57.0: ```shell cargo install onetun @@ -37,7 +37,7 @@ $ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.56.1: +You can also build onetun locally, using Rust ≥1.57.0: ```shell git clone https://github.com/aramperes/onetun && cd onetun From e62b7d30fe6eee5723b82f95ad0f8a39d2d92a4e Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 25 Sep 2022 17:29:04 -0400 Subject: [PATCH 103/165] release: v0.3.4 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e15aeb9..b9b7c16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,7 +431,7 @@ checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "onetun" -version = "0.3.3" +version = "0.3.4" dependencies = [ "anyhow", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index c378155..d38288f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.3" +version = "0.3.4" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From 76b6a6e346cbc858ca77fad6b7618ebad173b523 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Thu, 12 Jan 2023 01:40:04 -0500 Subject: [PATCH 104/165] Use bytes --- Cargo.lock | 1 + Cargo.toml | 1 + src/events.rs | 9 +++++---- src/tunnel/tcp.rs | 19 ++++++++++--------- src/tunnel/udp.rs | 7 ++++--- src/virtual_device.rs | 26 ++++++++++++++++++-------- src/virtual_iface/tcp.rs | 25 ++++++++++++++----------- src/virtual_iface/udp.rs | 13 +++++++------ src/wg.rs | 2 +- 9 files changed, 61 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9b7c16..d795033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,6 +437,7 @@ dependencies = [ "async-recursion", "async-trait", "boringtun", + "bytes", "clap", "futures", "log", diff --git a/Cargo.toml b/Cargo.toml index d38288f..40f2276 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ priority-queue = "1.2.0" smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } # forward boringtuns tracing events to log tracing = { version = "0.1.36", default-features = false, features = ["log"] } +bytes = "1" # bin-only dependencies clap = { version = "2.33", default-features = false, features = ["suggestions"], optional = true } diff --git a/src/events.rs b/src/events.rs index cd76a49..d6582ce 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,3 +1,4 @@ +use bytes::Bytes; use std::fmt::{Display, Formatter}; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; @@ -16,13 +17,13 @@ pub enum Event { /// A connection was dropped from the pool and should be closed in all interfaces. ClientConnectionDropped(VirtualPort), /// Data received by the local server that should be sent to the virtual server. - LocalData(PortForwardConfig, VirtualPort, Vec), + LocalData(PortForwardConfig, VirtualPort, Bytes), /// Data received by the remote server that should be sent to the local client. - RemoteData(VirtualPort, Vec), + RemoteData(VirtualPort, Bytes), /// IP packet received from the WireGuard tunnel that should be passed through the corresponding virtual device. - InboundInternetPacket(PortProtocol, Vec), + InboundInternetPacket(PortProtocol, Bytes), /// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device. - OutboundInternetPacket(Vec), + OutboundInternetPacket(Bytes), /// Notifies that a virtual device read an IP packet. VirtualDeviceFed(PortProtocol), } diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index 557f5ad..b5e1ec5 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -1,17 +1,18 @@ -use crate::config::{PortForwardConfig, PortProtocol}; -use crate::virtual_iface::VirtualPort; -use anyhow::Context; use std::collections::VecDeque; -use std::sync::Arc; -use tokio::net::{TcpListener, TcpStream}; - use std::ops::Range; +use std::sync::Arc; use std::time::Duration; -use crate::events::{Bus, Event}; +use anyhow::Context; +use bytes::BytesMut; use rand::seq::SliceRandom; use rand::thread_rng; use tokio::io::AsyncWriteExt; +use tokio::net::{TcpListener, TcpStream}; + +use crate::config::{PortForwardConfig, PortProtocol}; +use crate::events::{Bus, Event}; +use crate::virtual_iface::VirtualPort; const MAX_PACKET: usize = 65536; const MIN_PORT: u16 = 1000; @@ -81,7 +82,7 @@ async fn handle_tcp_proxy_connection( let mut endpoint = bus.new_endpoint(); endpoint.send(Event::ClientConnectionInitiated(port_forward, virtual_port)); - let mut buffer = Vec::with_capacity(MAX_PACKET); + let mut buffer = BytesMut::with_capacity(MAX_PACKET); loop { tokio::select! { readable_result = socket.readable() => { @@ -90,7 +91,7 @@ async fn handle_tcp_proxy_connection( match socket.try_read_buf(&mut buffer) { Ok(size) if size > 0 => { let data = Vec::from(&buffer[..size]); - endpoint.send(Event::LocalData(port_forward, virtual_port, data)); + endpoint.send(Event::LocalData(port_forward, virtual_port, data.into())); // Reset buffer buffer.clear(); } diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index 1d25914..32fef15 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -4,14 +4,15 @@ use std::ops::Range; use std::sync::Arc; use std::time::Instant; -use crate::events::{Bus, Event}; use anyhow::Context; +use bytes::Bytes; use priority_queue::double_priority_queue::DoublePriorityQueue; use rand::seq::SliceRandom; use rand::thread_rng; use tokio::net::UdpSocket; use crate::config::{PortForwardConfig, PortProtocol}; +use crate::events::{Bus, Event}; use crate::virtual_iface::VirtualPort; const MAX_PACKET: usize = 65536; @@ -98,7 +99,7 @@ async fn next_udp_datagram( socket: &UdpSocket, buffer: &mut [u8], port_pool: UdpPortPool, -) -> anyhow::Result)>> { +) -> anyhow::Result> { let (size, peer_addr) = socket .recv_from(buffer) .await @@ -126,7 +127,7 @@ async fn next_udp_datagram( port_pool.update_last_transmit(port).await; let data = buffer[..size].to_vec(); - Ok(Some((port, data))) + Ok(Some((port, data.into()))) } /// A pool of virtual ports available for TCP connections. diff --git a/src/virtual_device.rs b/src/virtual_device.rs index e0e7e4d..0054690 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -1,10 +1,13 @@ +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; + +use bytes::{BufMut, Bytes, BytesMut}; +use smoltcp::phy::{Device, DeviceCapabilities, Medium}; +use smoltcp::time::Instant; + use crate::config::PortProtocol; use crate::events::{BusSender, Event}; use crate::Bus; -use smoltcp::phy::{Device, DeviceCapabilities, Medium}; -use smoltcp::time::Instant; -use std::collections::VecDeque; -use std::sync::{Arc, Mutex}; /// A virtual device that processes IP packets through smoltcp and WireGuard. pub struct VirtualIpDevice { @@ -13,7 +16,7 @@ pub struct VirtualIpDevice { /// Channel receiver for received IP packets. bus_sender: BusSender, /// Local queue for packets received from the bus that need to go through the smoltcp interface. - process_queue: Arc>>>, + process_queue: Arc>>, } impl VirtualIpDevice { @@ -63,7 +66,13 @@ impl<'a> Device<'a> for VirtualIpDevice { }; match next { Some(buffer) => Some(( - Self::RxToken { buffer }, + Self::RxToken { + buffer: { + let mut buf = BytesMut::new(); + buf.put(buffer); + buf + }, + }, Self::TxToken { sender: self.bus_sender.clone(), }, @@ -88,7 +97,7 @@ impl<'a> Device<'a> for VirtualIpDevice { #[doc(hidden)] pub struct RxToken { - buffer: Vec, + buffer: BytesMut, } impl smoltcp::phy::RxToken for RxToken { @@ -113,7 +122,8 @@ impl smoltcp::phy::TxToken for TxToken { let mut buffer = Vec::new(); buffer.resize(len, 0); let result = f(&mut buffer); - self.sender.send(Event::OutboundInternetPacket(buffer)); + self.sender + .send(Event::OutboundInternetPacket(buffer.into())); result } } diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 28eacac..32706a0 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -1,16 +1,19 @@ +use std::collections::{HashMap, HashSet, VecDeque}; +use std::net::IpAddr; +use std::time::Duration; + +use anyhow::Context; +use async_trait::async_trait; +use bytes::Bytes; +use smoltcp::iface::{InterfaceBuilder, SocketHandle}; +use smoltcp::socket::{TcpSocket, TcpSocketBuffer, TcpState}; +use smoltcp::wire::{IpAddress, IpCidr}; + use crate::config::{PortForwardConfig, PortProtocol}; use crate::events::Event; use crate::virtual_device::VirtualIpDevice; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::Bus; -use anyhow::Context; -use async_trait::async_trait; -use smoltcp::iface::{InterfaceBuilder, SocketHandle}; -use smoltcp::socket::{TcpSocket, TcpSocketBuffer, TcpState}; -use smoltcp::wire::{IpAddress, IpCidr}; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::net::IpAddr; -use std::time::Duration; const MAX_PACKET: usize = 65536; @@ -102,7 +105,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { let mut port_client_handle_map: HashMap = HashMap::new(); // Data packets to send from a virtual client - let mut send_queue: HashMap>> = HashMap::new(); + let mut send_queue: HashMap> = HashMap::new(); loop { tokio::select! { @@ -147,7 +150,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { if sent < total { // Sometimes only a subset is sent, so the rest needs to be sent on the next poll let tx_extra = Vec::from(&to_transfer_slice[sent..total]); - send_queue.push_front(tx_extra); + send_queue.push_front(tx_extra.into()); } } Err(e) => { @@ -162,7 +165,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } } if client_socket.can_recv() { - match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { + match client_socket.recv(|buffer| (buffer.len(), Bytes::from(buffer.to_vec()))) { Ok(data) => { debug!("[{}] Received {} bytes from virtual server", virtual_port, data.len()); if !data.is_empty() { diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index d939132..be63071 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -1,18 +1,19 @@ -use anyhow::Context; use std::collections::{HashMap, HashSet, VecDeque}; use std::net::IpAddr; +use std::time::Duration; -use crate::events::Event; -use crate::{Bus, PortProtocol}; +use anyhow::Context; use async_trait::async_trait; +use bytes::Bytes; use smoltcp::iface::{InterfaceBuilder, SocketHandle}; use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; use smoltcp::wire::{IpAddress, IpCidr}; -use std::time::Duration; use crate::config::PortForwardConfig; +use crate::events::Event; use crate::virtual_device::VirtualIpDevice; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; +use crate::{Bus, PortProtocol}; const MAX_PACKET: usize = 65536; @@ -114,7 +115,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { let mut port_client_handle_map: HashMap = HashMap::new(); // Data packets to send from a virtual client - let mut send_queue: HashMap)>> = + let mut send_queue: HashMap> = HashMap::new(); loop { @@ -158,7 +159,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { match client_socket.recv() { Ok((data, _peer)) => { if !data.is_empty() { - endpoint.send(Event::RemoteData(*virtual_port, data.to_vec())); + endpoint.send(Event::RemoteData(*virtual_port, data.to_vec().into())); } } Err(e) => { diff --git a/src/wg.rs b/src/wg.rs index 18ef7d5..0646606 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -209,7 +209,7 @@ impl WireGuardTunnel { trace_ip_packet("Received IP packet", packet); if let Some(proto) = self.route_protocol(packet) { - endpoint.send(Event::InboundInternetPacket(proto, packet.into())); + endpoint.send(Event::InboundInternetPacket(proto, packet.to_vec().into())); } } _ => {} From fa634a08dc4610e802e497da2509fb7c4e582f0f Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Thu, 12 Jan 2023 01:43:32 -0500 Subject: [PATCH 105/165] Fix a clippy warning --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 9d9732d..93d9129 100644 --- a/src/config.rs +++ b/src/config.rs @@ -329,7 +329,7 @@ fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { use std::fs::File; use std::os::unix::fs::MetadataExt; - let mode = File::open(&path).ok()?.metadata().ok()?.mode(); + let mode = File::open(path).ok()?.metadata().ok()?.mode(); Some((mode & 0o40 > 0, mode & 0o4 > 0)) } From 4f935c5a2d7861337b0a367e93f215ed6fb6cfd3 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Thu, 12 Jan 2023 02:38:36 -0500 Subject: [PATCH 106/165] reorder dep --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 40f2276..8fa8385 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,10 @@ nom = "7" async-trait = "0.1.51" priority-queue = "1.2.0" smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } +bytes = "1" + # forward boringtuns tracing events to log tracing = { version = "0.1.36", default-features = false, features = ["log"] } -bytes = "1" # bin-only dependencies clap = { version = "2.33", default-features = false, features = ["suggestions"], optional = true } From 43a20ef6b38331ff9f810b0e2472056c3cdd2ac9 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Thu, 12 Jan 2023 02:53:56 -0500 Subject: [PATCH 107/165] Update dependencies --- Cargo.lock | 220 +++++++++++++++++++++++++++++------------------------ Cargo.toml | 16 ++-- 2 files changed, 129 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d795033..c8aa0ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-recursion" @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" dependencies = [ "proc-macro2", "quote", @@ -45,7 +45,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -58,9 +58,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" @@ -100,15 +100,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cesu8" @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -174,9 +174,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -184,15 +184,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -201,15 +201,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -218,21 +218,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -272,6 +272,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -289,9 +298,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -350,9 +359,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.133" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "lock_api" @@ -393,9 +402,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", @@ -405,9 +414,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -415,19 +424,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "onetun" @@ -462,9 +471,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", @@ -487,9 +496,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_env_logger" @@ -503,9 +512,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "1.2.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "815082d99af3acc75a3e67efd2a07f72e67b4e81b4344eb8ca34c6ebf3dfa9c5" +checksum = "d7685ca4cc0b3ad748c22ce6803e23b55b9206ef7715b965ebeaf41639238fdc" dependencies = [ "autocfg", "indexmap", @@ -513,9 +522,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] @@ -528,9 +537,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -576,9 +585,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -587,9 +596,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "ring" @@ -632,15 +641,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smoltcp" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72165c4af59f5f19c7fb774b88b95660591b612380305b5f4503157341a9f7ee" +checksum = "ee34c1e1bfc7e9206cc0fb8030a90129b4e319ab53856249bb27642cab914fb3" dependencies = [ "bitflags", "byteorder", @@ -672,9 +681,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.100" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -701,18 +710,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -721,9 +730,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.21.1" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" +checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" dependencies = [ "autocfg", "bytes", @@ -731,18 +740,17 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -751,9 +759,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -764,9 +772,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -775,18 +783,18 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-width" @@ -920,43 +928,57 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows_aarch64_gnullvm" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index 8fa8385..6ec0b5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,21 +15,21 @@ boringtun = { version = "0.4.0", default-features = false } log = "0.4" anyhow = "1" tokio = { version = "1", features = [ "rt", "sync", "io-util", "net", "time", "fs", "macros" ] } -futures = "0.3.17" -rand = "0.8.4" +futures = "0.3" +rand = "0.8" nom = "7" -async-trait = "0.1.51" -priority-queue = "1.2.0" -smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } +async-trait = "0.1" +priority-queue = "1.3.0" +smoltcp = { version = "0.8.2", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } bytes = "1" # forward boringtuns tracing events to log -tracing = { version = "0.1.36", default-features = false, features = ["log"] } +tracing = { version = "0.1", default-features = false, features = ["log"] } # bin-only dependencies -clap = { version = "2.33", default-features = false, features = ["suggestions"], optional = true } +clap = { version = "2.34", default-features = false, features = ["suggestions"], optional = true } pretty_env_logger = { version = "0.4", optional = true } -async-recursion = "1.0.0" +async-recursion = "1.0" [features] pcap = [] From 653c314409c017e47f3338552e7998196a3b6de9 Mon Sep 17 00:00:00 2001 From: George Hopkins Date: Mon, 2 Oct 2023 16:24:37 +0200 Subject: [PATCH 108/165] Support pre-shared key --- Cargo.toml | 1 + src/config.rs | 24 ++++++++++++++++++++++++ src/wg.rs | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6ec0b5e..4dee3d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ async-trait = "0.1" priority-queue = "1.3.0" smoltcp = { version = "0.8.2", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } bytes = "1" +base64 = "0.21" # forward boringtuns tracing events to log tracing = { version = "0.1", default-features = false, features = ["log"] } diff --git a/src/config.rs b/src/config.rs index 93d9129..54839cf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::sync::Arc; use anyhow::Context; +use base64::prelude::{Engine as _, BASE64_STANDARD}; pub use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; @@ -17,6 +18,7 @@ pub struct Config { pub remote_port_forwards: Vec, pub private_key: Arc, pub endpoint_public_key: Arc, + pub endpoint_preshared_key: Option<[u8; 32]>, pub endpoint_addr: SocketAddr, pub endpoint_bind_addr: SocketAddr, pub source_peer_ip: IpAddr, @@ -73,6 +75,12 @@ impl Config { .long("endpoint-public-key") .env("ONETUN_ENDPOINT_PUBLIC_KEY") .help("The public key of the WireGuard endpoint (remote)."), + Arg::with_name("endpoint-preshared-key") + .required(false) + .takes_value(true) + .long("endpoint-preshared-key") + .env("ONETUN_ENDPOINT_PRESHARED_KEY") + .help("The pre-shared key of the WireGuard endpoint (remote)."), Arg::with_name("endpoint-addr") .required(true) .takes_value(true) @@ -264,6 +272,9 @@ impl Config { parse_public_key(matches.value_of("endpoint-public-key")) .with_context(|| "Invalid endpoint public key")?, ), + endpoint_preshared_key: parse_preshared_key( + matches.value_of("endpoint-preshared-key"), + )?, endpoint_addr, endpoint_bind_addr, source_peer_ip, @@ -304,6 +315,19 @@ fn parse_public_key(s: Option<&str>) -> anyhow::Result { .with_context(|| "Invalid public key") } +fn parse_preshared_key(s: Option<&str>) -> anyhow::Result> { + if let Some(s) = s { + let psk = BASE64_STANDARD + .decode(s) + .with_context(|| "Invalid pre-shared key")?; + Ok(Some(psk.try_into().map_err(|_| { + anyhow::anyhow!("Unsupported pre-shared key") + })?)) + } else { + Ok(None) + } +} + fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { if let Some(s) = s { let parsed: u16 = s.parse().with_context(|| { diff --git a/src/wg.rs b/src/wg.rs index 0646606..0f2278a 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -221,7 +221,7 @@ impl WireGuardTunnel { Tunn::new( config.private_key.clone(), config.endpoint_public_key.clone(), - None, + config.endpoint_preshared_key, config.keepalive_seconds, 0, None, From 6f143280d1712ef57cff29402ab1d436d7b2d395 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:07:37 -0400 Subject: [PATCH 109/165] Pin older version of base64 for now --- Cargo.lock | 1 + Cargo.toml | 2 +- src/config.rs | 5 +---- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8aa0ac..3578048 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,6 +445,7 @@ dependencies = [ "anyhow", "async-recursion", "async-trait", + "base64", "boringtun", "bytes", "clap", diff --git a/Cargo.toml b/Cargo.toml index 4dee3d7..e9054eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ async-trait = "0.1" priority-queue = "1.3.0" smoltcp = { version = "0.8.2", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } bytes = "1" -base64 = "0.21" +base64 = "0.13" # forward boringtuns tracing events to log tracing = { version = "0.1", default-features = false, features = ["log"] } diff --git a/src/config.rs b/src/config.rs index 54839cf..576923a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,6 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::sync::Arc; use anyhow::Context; -use base64::prelude::{Engine as _, BASE64_STANDARD}; pub use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; @@ -317,9 +316,7 @@ fn parse_public_key(s: Option<&str>) -> anyhow::Result { fn parse_preshared_key(s: Option<&str>) -> anyhow::Result> { if let Some(s) = s { - let psk = BASE64_STANDARD - .decode(s) - .with_context(|| "Invalid pre-shared key")?; + let psk = base64::decode(s).with_context(|| "Invalid pre-shared key")?; Ok(Some(psk.try_into().map_err(|_| { anyhow::anyhow!("Unsupported pre-shared key") })?)) From 1333ea8a7cfb384b6676b1d3b747d6318c1b7aad Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:21:32 -0400 Subject: [PATCH 110/165] Rename option to --preshared-key and add to README --- README.md | 7 +++++++ src/config.rs | 14 ++++++-------- src/wg.rs | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index eab9f07..077034f 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,13 @@ You can bind to a static address instead using `--endpoint-bind-addr`: onetun --endpoint-bind-addr 0.0.0.0:51820 --endpoint-addr 140.30.3.182:51820 [...] ``` +The security of the WireGuard connection can be further enhanced with a **pre-shared key** (PSK). You can generate such a key with the `wg genpsk` command, and provide it using `--preshared-key`. +The peer must also have this key configured using the `PresharedKey` option. + +```shell +onetun --preshared-key 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' [...] +``` + ## Architecture **In short:** onetun uses [smoltcp's](https://github.com/smoltcp-rs/smoltcp) TCP/IP and UDP stack to generate IP packets diff --git a/src/config.rs b/src/config.rs index 576923a..467cf01 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,7 +17,7 @@ pub struct Config { pub remote_port_forwards: Vec, pub private_key: Arc, pub endpoint_public_key: Arc, - pub endpoint_preshared_key: Option<[u8; 32]>, + pub preshared_key: Option<[u8; 32]>, pub endpoint_addr: SocketAddr, pub endpoint_bind_addr: SocketAddr, pub source_peer_ip: IpAddr, @@ -74,12 +74,12 @@ impl Config { .long("endpoint-public-key") .env("ONETUN_ENDPOINT_PUBLIC_KEY") .help("The public key of the WireGuard endpoint (remote)."), - Arg::with_name("endpoint-preshared-key") + Arg::with_name("preshared-key") .required(false) .takes_value(true) - .long("endpoint-preshared-key") - .env("ONETUN_ENDPOINT_PRESHARED_KEY") - .help("The pre-shared key of the WireGuard endpoint (remote)."), + .long("preshared-key") + .env("ONETUN_PRESHARED_KEY") + .help("The pre-shared key (PSK) as configured with the peer."), Arg::with_name("endpoint-addr") .required(true) .takes_value(true) @@ -271,9 +271,7 @@ impl Config { parse_public_key(matches.value_of("endpoint-public-key")) .with_context(|| "Invalid endpoint public key")?, ), - endpoint_preshared_key: parse_preshared_key( - matches.value_of("endpoint-preshared-key"), - )?, + preshared_key: parse_preshared_key(matches.value_of("preshared-key"))?, endpoint_addr, endpoint_bind_addr, source_peer_ip, diff --git a/src/wg.rs b/src/wg.rs index 0f2278a..14efa23 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -221,7 +221,7 @@ impl WireGuardTunnel { Tunn::new( config.private_key.clone(), config.endpoint_public_key.clone(), - config.endpoint_preshared_key, + config.preshared_key, config.keepalive_seconds, 0, None, From 07e895c770791c1548eb73fee30adbbb5aafc79a Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:37:18 -0400 Subject: [PATCH 111/165] release: v0.3.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3578048..edb5aad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -440,7 +440,7 @@ checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "onetun" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index e9054eb..baf0a5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.4" +version = "0.3.5" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From 2896a4fcdb97f2ba28e8f6cfe1c8b3d771c2e043 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:55:05 -0400 Subject: [PATCH 112/165] Update dependencies and bump MSRV to 1.63 --- .github/workflows/build.yml | 4 +- Cargo.lock | 371 ++++++++++++++++++++++-------------- Dockerfile | 2 +- README.md | 4 +- 4 files changed, 228 insertions(+), 153 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f931667..ed1fa53 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.57.0 + - 1.63.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.57.0 + - 1.63.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/Cargo.lock b/Cargo.lock index edb5aad..0b0dd37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,25 +3,40 @@ version = 3 [[package]] -name = "aho-corasick" -version = "0.7.20" +name = "addr2line" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-recursion" -version = "1.0.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", @@ -30,9 +45,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.61" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", @@ -56,6 +71,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.1" @@ -88,9 +118,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" @@ -100,15 +130,18 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cesu8" @@ -159,9 +192,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -174,9 +207,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -184,15 +217,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -201,15 +234,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", @@ -218,21 +251,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -248,15 +281,21 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "hashbrown" version = "0.12.3" @@ -274,12 +313,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -298,9 +334,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -350,24 +386,24 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.139" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -375,12 +411,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "managed" @@ -390,9 +423,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "minimal-lexical" @@ -401,22 +434,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "mio" -version = "0.8.5" +name = "miniz_oxide" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi", "windows-sys", ] [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -424,19 +465,28 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.3", "libc", ] [[package]] -name = "once_cell" -version = "1.17.0" +name = "object" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "onetun" @@ -472,22 +522,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-targets", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -513,9 +563,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7685ca4cc0b3ad748c22ce6803e23b55b9206ef7715b965ebeaf41639238fdc" +checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61" dependencies = [ "autocfg", "indexmap", @@ -523,9 +573,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -538,9 +588,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.23" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -577,18 +627,30 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", @@ -597,9 +659,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ring" @@ -616,6 +678,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "same-file" version = "1.0.6" @@ -627,24 +695,24 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smoltcp" @@ -660,12 +728,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", - "winapi", + "windows-sys", ] [[package]] @@ -682,9 +750,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.107" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -693,9 +761,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] @@ -711,18 +779,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", @@ -731,14 +799,13 @@ dependencies = [ [[package]] name = "tokio" -version = "1.24.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", @@ -749,9 +816,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", @@ -773,9 +840,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", @@ -784,24 +851,24 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "untrusted" @@ -817,12 +884,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -834,9 +900,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -844,9 +910,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -859,9 +925,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -869,9 +935,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -882,15 +948,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -914,9 +980,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -929,9 +995,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -944,42 +1019,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Dockerfile b/Dockerfile index 280ebb5..2863097 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.57.0 as cargo-build +FROM rust:1.63.0 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml diff --git a/README.md b/README.md index 077034f..d2dce6a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.57.0: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.63.0: ```shell cargo install onetun @@ -37,7 +37,7 @@ $ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.57.0: +You can also build onetun locally, using Rust ≥1.63.0: ```shell git clone https://github.com/aramperes/onetun && cd onetun From c5e803192f5fb84bbad6531b7ef6c0765d7fbcd6 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:57:15 -0400 Subject: [PATCH 113/165] Disable macos package install --- .github/ci/macos-install-packages | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ci/macos-install-packages b/.github/ci/macos-install-packages index afb1102..a4ece81 100755 --- a/.github/ci/macos-install-packages +++ b/.github/ci/macos-install-packages @@ -1,6 +1,6 @@ #!/bin/sh -brew install asciidoctor +# brew install asciidoctor -brew install openssl@1.1 -cp /usr/local/opt/openssl@1.1/lib/pkgconfig/*.pc /usr/local/lib/pkgconfig/ +# brew install openssl@1.1 +# cp /usr/local/opt/openssl@1.1/lib/pkgconfig/*.pc /usr/local/lib/pkgconfig/ From 61da97f4aa3d8429bc3acb31014b82f2afa5de5e Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:05:30 -0400 Subject: [PATCH 114/165] Update release action to latest Ubuntu --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3917b6..bd4b468 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,7 @@ jobs: build: [ linux-amd64, macos-intel, windows ] include: - build: linux-amd64 - os: ubuntu-18.04 + os: ubuntu-latest rust: stable target: x86_64-unknown-linux-musl - build: macos-intel @@ -97,7 +97,7 @@ jobs: fetch-depth: 1 - name: Install packages (Ubuntu) - if: matrix.os == 'ubuntu-18.04' + if: matrix.os == 'ubuntu-latest' run: | .github/ci/ubuntu-install-packages - name: Install packages (macOS) From 0a06df59f9fbff2dd1b5ebb20f21204175e93e69 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:17:52 -0400 Subject: [PATCH 115/165] Update copyright year --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 72d8434..a4f6b7c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2022 Aram Peres +Copyright (c) 2023 Aram Peres Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d2dce6a..f695921 100644 --- a/README.md +++ b/README.md @@ -261,4 +261,4 @@ All in all, I would not recommend using UDP forwarding for public services, sinc ## License -MIT License. See `LICENSE` for details. Copyright © 2021-2022 Aram Peres. +MIT License. See `LICENSE` for details. Copyright © 2023 Aram Peres. From 998d1cfc8dbee88610ae6488f1eb0fdff3e4de0f Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:51:18 -0400 Subject: [PATCH 116/165] Add maintenance disclaimer --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index f695921..0c5fc36 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,13 @@ if the least recently used port hasn't been used for a certain amount of time. I All in all, I would not recommend using UDP forwarding for public services, since it's most likely prone to simple DoS or DDoS. +## Contributing and Maintenance + +I will gladly accept contributions to onetun, and set aside time to review all pull-requests. +Please consider opening a GitHub issue if you are unsure if your contribution is within the scope of the project. + +**Disclaimer**: I do not have enough personal time to actively maintain onetun besides open-source contributions. + ## License MIT License. See `LICENSE` for details. Copyright © 2023 Aram Peres. From 9f53198f170a879541b56ffb48bd2d12855065a5 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:55:24 -0400 Subject: [PATCH 117/165] Remove $ from README examples --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0c5fc36..91e61a6 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ the [Releases](https://github.com/aramperes/onetun/releases) page. You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun): ```shell -$ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ +docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` @@ -110,7 +110,7 @@ INFO onetun > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3 Which means you can now access the port locally! ```shell -$ curl 127.0.0.1:8080 +curl 127.0.0.1:8080 Hello world! ``` @@ -119,7 +119,7 @@ Hello world! **onetun** supports running multiple tunnels in parallel. For example: ```shell -$ onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081 +onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081 INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -132,10 +132,10 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [ both protocols on the same port (note that this opens 2 separate tunnels, just on the same port) ```shell -$ onetun 127.0.0.1:8080:192.168.4.2:8080:UDP +onetun 127.0.0.1:8080:192.168.4.2:8080:UDP INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -$ onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP +onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -148,7 +148,7 @@ it in any production capacity. **onetun** supports both IPv4 and IPv6. In fact, you can use onetun to forward some IP version to another, e.g. 6-to-4: ```shell -$ onetun [::1]:8080:192.168.4.2:8080 +onetun [::1]:8080:192.168.4.2:8080 INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -156,7 +156,7 @@ Note that each tunnel can only support one "source" IP version and one "destinat both IPv4 and IPv6 on the same port, you should create a second port-forward: ```shell -$ onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080 +onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080 INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` @@ -167,7 +167,7 @@ For debugging purposes, you can enable the capture of IP packets sent between on The output is a libpcap capture file that can be viewed with Wireshark. ```shell -$ onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080 +onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080 INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` From 1997ae7ea88e79b3802cc7f67fce497614c18fdb Mon Sep 17 00:00:00 2001 From: Marco Nalon Date: Wed, 20 Dec 2023 16:59:27 +0100 Subject: [PATCH 118/165] chore: update Dockerfile rust version 1.63.0 -> 1.65.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2863097..1765243 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.63.0 as cargo-build +FROM rust:1.65.0 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml From 7200cc07e78042e92f76f981189b8eab941ba1de Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:31:39 -0500 Subject: [PATCH 119/165] chore: update MSRV to 1.65.0 --- .github/workflows/build.yml | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed1fa53..a573030 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.63.0 + - 1.65.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.63.0 + - 1.65.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/README.md b/README.md index 91e61a6..c4d906e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.63.0: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.65.0: ```shell cargo install onetun @@ -37,7 +37,7 @@ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.63.0: +You can also build onetun locally, using Rust ≥1.65.0: ```shell git clone https://github.com/aramperes/onetun && cd onetun From 731218d943b875bae32517be3fc9df9d8a5bf7d3 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:16:57 -0500 Subject: [PATCH 120/165] Fix new clippy warnings --- src/virtual_device.rs | 3 +-- src/virtual_iface/tcp.rs | 2 +- src/virtual_iface/udp.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 0054690..57ad3a1 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -119,8 +119,7 @@ impl smoltcp::phy::TxToken for TxToken { where F: FnOnce(&mut [u8]) -> smoltcp::Result, { - let mut buffer = Vec::new(); - buffer.resize(len, 0); + let mut buffer = vec![0; len]; let result = f(&mut buffer); self.sender .send(Event::OutboundInternetPacket(buffer.into())); diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 32706a0..8a7a509 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -229,7 +229,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { next_poll = None; } } - Event::VirtualDeviceFed(protocol) if protocol == PortProtocol::Tcp => { + Event::VirtualDeviceFed(PortProtocol::Tcp) => { next_poll = None; } _ => {} diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index be63071..a3d1652 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -198,7 +198,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } next_poll = None; } - Event::VirtualDeviceFed(protocol) if protocol == PortProtocol::Udp => { + Event::VirtualDeviceFed(PortProtocol::Udp) => { next_poll = None; } _ => {} From 29fb98f02bbbf00f8fccebd394102ee87f89e9f0 Mon Sep 17 00:00:00 2001 From: Luca Barbato Date: Fri, 15 Dec 2023 07:19:32 +0100 Subject: [PATCH 121/165] Update deps --- Cargo.lock | 169 ++++++++++++++++++++++++++--------------------------- 1 file changed, 84 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b0dd37..5e02211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", @@ -124,9 +124,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -192,9 +192,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -217,15 +217,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -234,15 +234,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", @@ -251,21 +251,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -281,9 +281,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -292,9 +292,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hashbrown" @@ -386,24 +386,24 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.148" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -444,9 +444,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", @@ -484,9 +484,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "onetun" @@ -522,9 +522,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", @@ -573,9 +573,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -627,18 +627,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.9.6" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -648,9 +648,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ring" @@ -710,9 +710,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smoltcp" @@ -728,9 +728,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -750,9 +750,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "2.0.37" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -761,9 +761,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -779,18 +779,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -799,9 +799,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.32.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", @@ -816,9 +816,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -827,11 +827,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -840,9 +839,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", @@ -851,9 +850,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -900,9 +899,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -910,9 +909,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -925,9 +924,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -935,9 +934,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -948,15 +947,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", From 1613d2bb5c832564804d0939310f3441129d4727 Mon Sep 17 00:00:00 2001 From: Luca Barbato Date: Fri, 15 Dec 2023 07:56:47 +0100 Subject: [PATCH 122/165] Bump clap version --- Cargo.lock | 181 +++++++++++++++++++++++++++++++++++++++++--------- Cargo.toml | 2 +- src/config.rs | 117 ++++++++++++++++---------------- 3 files changed, 211 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e02211..a2270ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + [[package]] name = "anyhow" version = "1.0.75" @@ -98,6 +104,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "boringtun" version = "0.4.0" @@ -157,16 +169,31 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.34.0" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ - "bitflags", - "strsim", - "textwrap", - "unicode-width", + "clap_builder", ] +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "combine" version = "4.6.6" @@ -190,6 +217,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "futures" version = "0.3.29" @@ -399,6 +436,12 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + [[package]] name = "lock_api" version = "0.4.11" @@ -450,7 +493,7 @@ checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -530,7 +573,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -631,7 +674,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -684,6 +727,19 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "same-file" version = "1.0.6" @@ -720,7 +776,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee34c1e1bfc7e9206cc0fb8030a90129b4e319ab53856249bb27642cab914fb3" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "log", "managed", @@ -733,7 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -744,9 +800,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -769,12 +825,13 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "terminal_size" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "unicode-width", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -811,7 +868,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -863,12 +920,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - [[package]] name = "untrusted" version = "0.7.1" @@ -998,7 +1049,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1007,13 +1067,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1022,38 +1097,80 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index baf0a5d..48c0aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ base64 = "0.13" tracing = { version = "0.1", default-features = false, features = ["log"] } # bin-only dependencies -clap = { version = "2.34", default-features = false, features = ["suggestions"], optional = true } +clap = { version = "4.4.11", default-features = false, features = ["suggestions", "std", "env", "help", "wrap_help"], optional = true } pretty_env_logger = { version = "0.4", optional = true } async-recursion = "1.0" diff --git a/src/config.rs b/src/config.rs index 467cf01..11425da 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,18 +31,17 @@ pub struct Config { impl Config { #[cfg(feature = "bin")] pub fn from_args() -> anyhow::Result { - use clap::{App, Arg}; + use clap::{Arg, Command}; let mut warnings = vec![]; - let matches = App::new("onetun") + let matches = Command::new("onetun") .author("Aram Peres ") .version(env!("CARGO_PKG_VERSION")) .args(&[ - Arg::with_name("PORT_FORWARD") + Arg::new("PORT_FORWARD") .required(false) - .multiple(true) - .takes_value(true) + .num_args(1..) .help("Port forward configurations. The format of each argument is [src_host:]::[:TCP,UDP,...], \ where [src_host] is the local IP to listen on, is the local port to listen on, is the remote peer IP to forward to, and is the remote port to forward to. \ Environment variables of the form 'ONETUN_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1.\n\ @@ -56,80 +55,79 @@ impl Config { \tlocalhost:8080:192.168.4.1:8081:TCP\n\ \tlocalhost:8080:peer.intranet:8081:TCP\ "), - Arg::with_name("private-key") - .required_unless("private-key-file") - .takes_value(true) + Arg::new("private-key") + .conflicts_with("private-key-file") + .num_args(1) .long("private-key") .env("ONETUN_PRIVATE_KEY") .help("The private key of this peer. The corresponding public key should be registered in the WireGuard endpoint. \ You can also use '--private-key-file' to specify a file containing the key instead."), - Arg::with_name("private-key-file") - .takes_value(true) + Arg::new("private-key-file") + .num_args(1) .long("private-key-file") .env("ONETUN_PRIVATE_KEY_FILE") .help("The path to a file containing the private key of this peer. The corresponding public key should be registered in the WireGuard endpoint."), - Arg::with_name("endpoint-public-key") + Arg::new("endpoint-public-key") .required(true) - .takes_value(true) + .num_args(1) .long("endpoint-public-key") .env("ONETUN_ENDPOINT_PUBLIC_KEY") .help("The public key of the WireGuard endpoint (remote)."), - Arg::with_name("preshared-key") + Arg::new("preshared-key") .required(false) - .takes_value(true) + .num_args(1) .long("preshared-key") .env("ONETUN_PRESHARED_KEY") .help("The pre-shared key (PSK) as configured with the peer."), - Arg::with_name("endpoint-addr") + Arg::new("endpoint-addr") .required(true) - .takes_value(true) + .num_args(1) .long("endpoint-addr") .env("ONETUN_ENDPOINT_ADDR") .help("The address (IP + port) of the WireGuard endpoint (remote). Example: 1.2.3.4:51820"), - Arg::with_name("endpoint-bind-addr") + Arg::new("endpoint-bind-addr") .required(false) - .takes_value(true) + .num_args(1) .long("endpoint-bind-addr") .env("ONETUN_ENDPOINT_BIND_ADDR") .help("The address (IP + port) used to bind the local UDP socket for the WireGuard tunnel. Example: 1.2.3.4:30000. Defaults to 0.0.0.0:0 for IPv4 endpoints, or [::]:0 for IPv6 endpoints."), - Arg::with_name("source-peer-ip") + Arg::new("source-peer-ip") .required(true) - .takes_value(true) + .num_args(1) .long("source-peer-ip") .env("ONETUN_SOURCE_PEER_IP") .help("The source IP to identify this peer as (local). Example: 192.168.4.3"), - Arg::with_name("keep-alive") + Arg::new("keep-alive") .required(false) - .takes_value(true) + .num_args(1) .long("keep-alive") .env("ONETUN_KEEP_ALIVE") .help("Configures a persistent keep-alive for the WireGuard tunnel, in seconds."), - Arg::with_name("max-transmission-unit") + Arg::new("max-transmission-unit") .required(false) - .takes_value(true) + .num_args(1) .long("max-transmission-unit") .env("ONETUN_MTU") .default_value("1420") .help("Configures the max-transmission-unit (MTU) of the WireGuard tunnel."), - Arg::with_name("log") + Arg::new("log") .required(false) - .takes_value(true) + .num_args(1) .long("log") .env("ONETUN_LOG") .default_value("info") .help("Configures the log level and format."), - Arg::with_name("pcap") + Arg::new("pcap") .required(false) - .takes_value(true) + .num_args(1) .long("pcap") .env("ONETUN_PCAP") .help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file."), - Arg::with_name("remote") + Arg::new("remote") .required(false) - .takes_value(true) - .multiple(true) + .num_args(1..) .long("remote") - .short("r") + .short('r') .help("Remote port forward configurations. The format of each argument is ::[:TCP,UDP,...], \ where is the port the other peers will reach the server with, is the IP to forward to, and is the port to forward to. \ The will be bound on onetun's peer IP, as specified by --source-peer-ip. If you pass a different value for here, it will be rejected.\n\ @@ -144,7 +142,7 @@ impl Config { // Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs let mut port_forward_strings = HashSet::new(); - if let Some(values) = matches.values_of("PORT_FORWARD") { + if let Some(values) = matches.get_many::("PORT_FORWARD") { for value in values { port_forward_strings.insert(value.to_owned()); } @@ -169,12 +167,12 @@ impl Config { .collect(); // Read source-peer-ip - let source_peer_ip = parse_ip(matches.value_of("source-peer-ip")) + let source_peer_ip = parse_ip(matches.get_one::("source-peer-ip")) .with_context(|| "Invalid source peer IP")?; // Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs let mut port_forward_strings = HashSet::new(); - if let Some(values) = matches.values_of("remote") { + if let Some(values) = matches.get_many::("remote") { for value in values { port_forward_strings.insert(value.to_owned()); } @@ -193,7 +191,7 @@ impl Config { .map(|s| { PortForwardConfig::from_notation( &s, - matches.value_of("source-peer-ip").unwrap(), + matches.get_one::("source-peer-ip").unwrap(), ) }) .collect(); @@ -216,7 +214,7 @@ impl Config { // Read private key from file or CLI argument let (group_readable, world_readable) = matches - .value_of("private-key-file") + .get_one::("private-key-file") .and_then(is_file_insecurely_readable) .unwrap_or_default(); if group_readable { @@ -226,7 +224,9 @@ impl Config { warnings.push("Private key file is world-readable. This is insecure.".into()); } - let private_key = if let Some(private_key_file) = matches.value_of("private-key-file") { + let private_key = if let Some(private_key_file) = + matches.get_one::("private-key-file") + { read_to_string(private_key_file) .map(|s| s.trim().to_string()) .with_context(|| "Failed to read private key file") @@ -236,15 +236,16 @@ impl Config { Use \"--private-key-file \", or the \"ONETUN_PRIVATE_KEY\" env variable instead.".into()); } matches - .value_of("private-key") - .map(String::from) + .get_one::("private-key") + .cloned() .with_context(|| "Missing private key") }?; - let endpoint_addr = parse_addr(matches.value_of("endpoint-addr")) + let endpoint_addr = parse_addr(matches.get_one::("endpoint-addr")) .with_context(|| "Invalid endpoint address")?; - let endpoint_bind_addr = if let Some(addr) = matches.value_of("endpoint-bind-addr") { + let endpoint_bind_addr = if let Some(addr) = matches.get_one::("endpoint-bind-addr") + { let addr = parse_addr(Some(addr)).with_context(|| "Invalid bind address")?; // Make sure the bind address and endpoint address are the same IP version if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() { @@ -268,33 +269,37 @@ impl Config { parse_private_key(&private_key).with_context(|| "Invalid private key")?, ), endpoint_public_key: Arc::new( - parse_public_key(matches.value_of("endpoint-public-key")) + parse_public_key(matches.get_one::("endpoint-public-key")) .with_context(|| "Invalid endpoint public key")?, ), - preshared_key: parse_preshared_key(matches.value_of("preshared-key"))?, + preshared_key: parse_preshared_key(matches.get_one::("preshared-key"))?, endpoint_addr, endpoint_bind_addr, source_peer_ip, - keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) + keepalive_seconds: parse_keep_alive(matches.get_one::("keep-alive")) .with_context(|| "Invalid keep-alive value")?, - max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit")) + max_transmission_unit: parse_mtu(matches.get_one::("max-transmission-unit")) .with_context(|| "Invalid max-transmission-unit value")?, - log: matches.value_of("log").unwrap_or_default().into(), - pcap_file: matches.value_of("pcap").map(String::from), + log: matches + .get_one::("log") + .cloned() + .unwrap_or_default(), + pcap_file: matches.get_one::("pcap").cloned(), warnings, }) } } -fn parse_addr(s: Option<&str>) -> anyhow::Result { +fn parse_addr>(s: Option) -> anyhow::Result { s.with_context(|| "Missing address")? + .as_ref() .to_socket_addrs() .with_context(|| "Invalid address")? .next() .with_context(|| "Could not lookup address") } -fn parse_ip(s: Option<&str>) -> anyhow::Result { +fn parse_ip(s: Option<&String>) -> anyhow::Result { s.with_context(|| "Missing IP")? .parse::() .with_context(|| "Invalid IP address") @@ -305,14 +310,14 @@ fn parse_private_key(s: &str) -> anyhow::Result { .map_err(|e| anyhow::anyhow!("{}", e)) } -fn parse_public_key(s: Option<&str>) -> anyhow::Result { +fn parse_public_key(s: Option<&String>) -> anyhow::Result { s.with_context(|| "Missing public key")? .parse::() .map_err(|e| anyhow::anyhow!("{}", e)) .with_context(|| "Invalid public key") } -fn parse_preshared_key(s: Option<&str>) -> anyhow::Result> { +fn parse_preshared_key(s: Option<&String>) -> anyhow::Result> { if let Some(s) = s { let psk = base64::decode(s).with_context(|| "Invalid pre-shared key")?; Ok(Some(psk.try_into().map_err(|_| { @@ -323,7 +328,7 @@ fn parse_preshared_key(s: Option<&str>) -> anyhow::Result> { } } -fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { +fn parse_keep_alive(s: Option<&String>) -> anyhow::Result> { if let Some(s) = s { let parsed: u16 = s.parse().with_context(|| { format!( @@ -337,14 +342,14 @@ fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { } } -fn parse_mtu(s: Option<&str>) -> anyhow::Result { +fn parse_mtu(s: Option<&String>) -> anyhow::Result { s.with_context(|| "Missing MTU")? .parse() .with_context(|| "Invalid MTU") } #[cfg(unix)] -fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { +fn is_file_insecurely_readable(path: &String) -> Option<(bool, bool)> { use std::fs::File; use std::os::unix::fs::MetadataExt; @@ -353,7 +358,7 @@ fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> { } #[cfg(not(unix))] -fn is_file_insecurely_readable(_path: &str) -> Option<(bool, bool)> { +fn is_file_insecurely_readable(_path: &String) -> Option<(bool, bool)> { // No good way to determine permissions on non-Unix target None } From a100f90a92ea900f7103f51aab4173e347ee85be Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:29:43 -0500 Subject: [PATCH 123/165] chore: update MSRV to 1.70.0 --- .github/workflows/build.yml | 4 ++-- .gitignore | 1 + Dockerfile | 2 +- README.md | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a573030..c85326b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.65.0 + - 1.70.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.65.0 + - 1.70.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index 0333ac1..7a1777c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .envrc *.log *.pcap +.DS_Store diff --git a/Dockerfile b/Dockerfile index 1765243..59ad657 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.65.0 as cargo-build +FROM rust:1.70.0 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml diff --git a/README.md b/README.md index c4d906e..b7e14fa 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.65.0: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.70.0: ```shell cargo install onetun @@ -37,7 +37,7 @@ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.65.0: +You can also build onetun locally, using Rust ≥1.70.0: ```shell git clone https://github.com/aramperes/onetun && cd onetun From ae15b4203c5638fbf17705ddfaf1829a6696e668 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:34:08 -0500 Subject: [PATCH 124/165] release: v0.3.6 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2270ec..7c8df37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,7 +533,7 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "onetun" -version = "0.3.5" +version = "0.3.6" dependencies = [ "anyhow", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 48c0aab..8a27b36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.5" +version = "0.3.6" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From 38fc217a29d36b77e6a5baf944d07b6e1b6d3b1f Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 21 Oct 2023 11:12:18 +0800 Subject: [PATCH 125/165] smoltcp version 0.10 applied --- Cargo.lock | 164 ++++++++++++++++++++++++++++++++++++--- Cargo.toml | 12 ++- src/virtual_device.rs | 34 ++++---- src/virtual_iface/tcp.rs | 100 +++++++++++++----------- src/virtual_iface/udp.rs | 107 +++++++++++++------------ src/wg.rs | 2 +- 6 files changed, 296 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c8df37..ed1457b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,7 +46,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.41", ] [[package]] @@ -57,7 +57,16 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.41", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", ] [[package]] @@ -204,6 +213,44 @@ dependencies = [ "memchr", ] +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "defmt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -283,7 +330,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.41", ] [[package]] @@ -333,12 +380,34 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -614,6 +683,30 @@ dependencies = [ "indexmap", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.70" @@ -715,7 +808,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted 0.7.1", "web-sys", "winapi", @@ -727,6 +820,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.28" @@ -755,6 +857,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "slab" version = "0.4.9" @@ -772,12 +880,15 @@ checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smoltcp" -version = "0.8.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee34c1e1bfc7e9206cc0fb8030a90129b4e319ab53856249bb27642cab914fb3" +checksum = "8d2e3a36ac8fea7b94e666dfa3871063d6e0a5c9d5d4fec9a1a6b7b6760f0229" dependencies = [ "bitflags 1.3.2", "byteorder", + "cfg-if", + "defmt", + "heapless", "log", "managed", ] @@ -798,12 +909,37 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.41" @@ -851,7 +987,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.41", ] [[package]] @@ -879,7 +1015,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.41", ] [[package]] @@ -902,7 +1038,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.41", ] [[package]] @@ -932,6 +1068,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "walkdir" version = "2.4.0" @@ -969,7 +1111,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -991,7 +1133,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 8a27b36..da35c45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,16 @@ futures = "0.3" rand = "0.8" nom = "7" async-trait = "0.1" -priority-queue = "1.3.0" -smoltcp = { version = "0.8.2", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] } +priority-queue = "1.3" +smoltcp = { version = "0.10", default-features = false, features = [ + "std", + "log", + "medium-ip", + "proto-ipv4", + "proto-ipv6", + "socket-udp", + "socket-tcp", +] } bytes = "1" base64 = "0.13" diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 57ad3a1..7af4d27 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -1,13 +1,15 @@ -use std::collections::VecDeque; -use std::sync::{Arc, Mutex}; - -use bytes::{BufMut, Bytes, BytesMut}; -use smoltcp::phy::{Device, DeviceCapabilities, Medium}; -use smoltcp::time::Instant; - use crate::config::PortProtocol; use crate::events::{BusSender, Event}; use crate::Bus; +use bytes::{BufMut, Bytes, BytesMut}; +use smoltcp::{ + phy::{DeviceCapabilities, Medium}, + time::Instant, +}; +use std::{ + collections::VecDeque, + sync::{Arc, Mutex}, +}; /// A virtual device that processes IP packets through smoltcp and WireGuard. pub struct VirtualIpDevice { @@ -52,11 +54,11 @@ impl VirtualIpDevice { } } -impl<'a> Device<'a> for VirtualIpDevice { - type RxToken = RxToken; - type TxToken = TxToken; +impl smoltcp::phy::Device for VirtualIpDevice { + type RxToken<'a> = RxToken where Self: 'a; + type TxToken<'a> = TxToken where Self: 'a; - fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { let next = { let mut queue = self .process_queue @@ -81,7 +83,7 @@ impl<'a> Device<'a> for VirtualIpDevice { } } - fn transmit(&'a mut self) -> Option { + fn transmit(&mut self, _timestamp: Instant) -> Option> { Some(TxToken { sender: self.bus_sender.clone(), }) @@ -101,9 +103,9 @@ pub struct RxToken { } impl smoltcp::phy::RxToken for RxToken { - fn consume(mut self, _timestamp: Instant, f: F) -> smoltcp::Result + fn consume(mut self, f: F) -> R where - F: FnOnce(&mut [u8]) -> smoltcp::Result, + F: FnOnce(&mut [u8]) -> R, { f(&mut self.buffer) } @@ -115,9 +117,9 @@ pub struct TxToken { } impl smoltcp::phy::TxToken for TxToken { - fn consume(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result + fn consume(self, len: usize, f: F) -> R where - F: FnOnce(&mut [u8]) -> smoltcp::Result, + F: FnOnce(&mut [u8]) -> R, { let mut buffer = vec![0; len]; let result = f(&mut buffer); diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 8a7a509..24957bd 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -1,30 +1,34 @@ -use std::collections::{HashMap, HashSet, VecDeque}; -use std::net::IpAddr; -use std::time::Duration; - -use anyhow::Context; -use async_trait::async_trait; -use bytes::Bytes; -use smoltcp::iface::{InterfaceBuilder, SocketHandle}; -use smoltcp::socket::{TcpSocket, TcpSocketBuffer, TcpState}; -use smoltcp::wire::{IpAddress, IpCidr}; - use crate::config::{PortForwardConfig, PortProtocol}; use crate::events::Event; use crate::virtual_device::VirtualIpDevice; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::Bus; +use anyhow::Context; +use async_trait::async_trait; +use bytes::Bytes; +use smoltcp::{ + iface::{Config, Interface, SocketHandle, SocketSet}, + socket::tcp, + time::Instant, + wire::{HardwareAddress, IpAddress, IpCidr}, +}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + net::IpAddr, + time::Duration, +}; const MAX_PACKET: usize = 65536; /// A virtual interface for proxying Layer 7 data to Layer 3 packets, and vice-versa. -pub struct TcpVirtualInterface { +pub struct TcpVirtualInterface<'a> { source_peer_ip: IpAddr, port_forwards: Vec, bus: Bus, + sockets: SocketSet<'a>, } -impl TcpVirtualInterface { +impl<'a> TcpVirtualInterface<'a> { /// Initialize the parameters for a new virtual interface. /// Use the `poll_loop()` future to start the virtual interface poll loop. pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { @@ -35,16 +39,17 @@ impl TcpVirtualInterface { .collect(), source_peer_ip, bus, + sockets: SocketSet::new([]), } } - fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { + fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { static mut TCP_SERVER_RX_DATA: [u8; 0] = []; static mut TCP_SERVER_TX_DATA: [u8; 0] = []; - let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); - let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); - let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); + let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + let mut socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); socket .listen(( @@ -56,12 +61,12 @@ impl TcpVirtualInterface { Ok(socket) } - fn new_client_socket() -> anyhow::Result> { + fn new_client_socket() -> anyhow::Result> { let rx_data = vec![0u8; MAX_PACKET]; let tx_data = vec![0u8; MAX_PACKET]; - let tcp_rx_buffer = TcpSocketBuffer::new(rx_data); - let tcp_tx_buffer = TcpSocketBuffer::new(tx_data); - let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + let tcp_rx_buffer = tcp::SocketBuffer::new(rx_data); + let tcp_tx_buffer = tcp::SocketBuffer::new(tx_data); + let socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); Ok(socket) } @@ -79,20 +84,31 @@ impl TcpVirtualInterface { } #[async_trait] -impl VirtualInterfacePoll for TcpVirtualInterface { - async fn poll_loop(self, device: VirtualIpDevice) -> anyhow::Result<()> { +impl VirtualInterfacePoll for TcpVirtualInterface<'_> { + async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { // Create CIDR block for source peer IP + each port forward IP let addresses = self.addresses(); + let config = Config::new(HardwareAddress::Ip); // Create virtual interface (contains smoltcp state machine) - let mut iface = InterfaceBuilder::new(device, vec![]) - .ip_addrs(addresses) - .finalize(); + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + addresses.iter().for_each(|addr| { + ip_addrs.push(*addr).unwrap(); + }); + }); + iface.set_any_ip(true); + + // Maps virtual port to its client socket handle + let mut port_client_handle_map: HashMap = HashMap::new(); // Create virtual server for each port forward for port_forward in self.port_forwards.iter() { let server_socket = TcpVirtualInterface::new_server_socket(*port_forward)?; - iface.add_socket(server_socket); + let handle = self.sockets.add(server_socket); + let virtual_port = + VirtualPort::new(port_forward.destination.port(), port_forward.protocol); + port_client_handle_map.insert(virtual_port, handle); } // The next time to poll the interface. Can be None for instant poll. @@ -101,9 +117,6 @@ impl VirtualInterfacePoll for TcpVirtualInterface { // Bus endpoint to read events let mut endpoint = self.bus.new_endpoint(); - // Maps virtual port to its client socket handle - let mut port_client_handle_map: HashMap = HashMap::new(); - // Data packets to send from a virtual client let mut send_queue: HashMap> = HashMap::new(); @@ -118,11 +131,11 @@ impl VirtualInterfacePoll for TcpVirtualInterface { // Find closed sockets port_client_handle_map.retain(|virtual_port, client_handle| { - let client_socket = iface.get_socket::(*client_handle); - if client_socket.state() == TcpState::Closed { + let client_socket = self.sockets.get_mut::(*client_handle); + if client_socket.state() == tcp::State::Closed { endpoint.send(Event::ClientConnectionDropped(*virtual_port)); send_queue.remove(virtual_port); - iface.remove_socket(*client_handle); + self.sockets.remove(*client_handle); false } else { // Not closed, retain @@ -130,16 +143,12 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } }); - match iface.poll(loop_start) { - Ok(processed) if processed => { - trace!("TCP virtual interface polled some packets to be processed"); - } - Err(e) => error!("TCP virtual interface poll error: {:?}", e), - _ => {} + if iface.poll(loop_start, &mut device, &mut self.sockets) { + log::trace!("TCP virtual interface polled some packets to be processed"); } for (virtual_port, client_handle) in port_client_handle_map.iter() { - let client_socket = iface.get_socket::(*client_handle); + let client_socket = self.sockets.get_mut::(*client_handle); if client_socket.can_send() { if let Some(send_queue) = send_queue.get_mut(virtual_port) { let to_transfer = send_queue.pop_front(); @@ -159,7 +168,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { ); } } - } else if client_socket.state() == TcpState::CloseWait { + } else if client_socket.state() == tcp::State::CloseWait { client_socket.close(); } } @@ -182,7 +191,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } // The virtual interface determines the next time to poll (this is to reduce unnecessary polls) - next_poll = match iface.poll_delay(loop_start) { + next_poll = match iface.poll_delay(loop_start, &self.sockets) { Some(smoltcp::time::Duration::ZERO) => None, Some(delay) => { trace!("TCP Virtual interface delayed next poll by {}", delay); @@ -195,13 +204,14 @@ impl VirtualInterfacePoll for TcpVirtualInterface { match event { Event::ClientConnectionInitiated(port_forward, virtual_port) => { let client_socket = TcpVirtualInterface::new_client_socket()?; - let client_handle = iface.add_socket(client_socket); + let client_handle = self.sockets.add(client_socket); // Add handle to map port_client_handle_map.insert(virtual_port, client_handle); send_queue.insert(virtual_port, VecDeque::new()); - let (client_socket, context) = iface.get_socket_and_context::(client_handle); + let client_socket = self.sockets.get_mut::(client_handle); + let context = iface.context(); client_socket .connect( @@ -218,7 +228,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } Event::ClientConnectionDropped(virtual_port) => { if let Some(client_handle) = port_client_handle_map.get(&virtual_port) { - let client_socket = iface.get_socket::(*client_handle); + let client_socket = self.sockets.get_mut::(*client_handle); client_socket.close(); next_poll = None; } diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index a3d1652..8426e4a 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -1,29 +1,33 @@ -use std::collections::{HashMap, HashSet, VecDeque}; -use std::net::IpAddr; -use std::time::Duration; - -use anyhow::Context; -use async_trait::async_trait; -use bytes::Bytes; -use smoltcp::iface::{InterfaceBuilder, SocketHandle}; -use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; -use smoltcp::wire::{IpAddress, IpCidr}; - use crate::config::PortForwardConfig; use crate::events::Event; use crate::virtual_device::VirtualIpDevice; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::{Bus, PortProtocol}; +use anyhow::Context; +use async_trait::async_trait; +use bytes::Bytes; +use smoltcp::{ + iface::{Config, Interface, SocketHandle, SocketSet}, + socket::udp::{self, UdpMetadata}, + time::Instant, + wire::{HardwareAddress, IpAddress, IpCidr}, +}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + net::IpAddr, + time::Duration, +}; const MAX_PACKET: usize = 65536; -pub struct UdpVirtualInterface { +pub struct UdpVirtualInterface<'a> { source_peer_ip: IpAddr, port_forwards: Vec, bus: Bus, + sockets: SocketSet<'a>, } -impl UdpVirtualInterface { +impl<'a> UdpVirtualInterface<'a> { /// Initialize the parameters for a new virtual interface. /// Use the `poll_loop()` future to start the virtual interface poll loop. pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { @@ -34,21 +38,24 @@ impl UdpVirtualInterface { .collect(), source_peer_ip, bus, + sockets: SocketSet::new([]), } } - fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { - static mut UDP_SERVER_RX_META: [UdpPacketMetadata; 0] = []; + fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { + static mut UDP_SERVER_RX_META: [udp::PacketMetadata; 0] = []; static mut UDP_SERVER_RX_DATA: [u8; 0] = []; - static mut UDP_SERVER_TX_META: [UdpPacketMetadata; 0] = []; + static mut UDP_SERVER_TX_META: [udp::PacketMetadata; 0] = []; static mut UDP_SERVER_TX_DATA: [u8; 0] = []; - let udp_rx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe { - &mut UDP_SERVER_RX_DATA[..] - }); - let udp_tx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe { - &mut UDP_SERVER_TX_DATA[..] - }); - let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); + let udp_rx_buffer = + udp::PacketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe { + &mut UDP_SERVER_RX_DATA[..] + }); + let udp_tx_buffer = + udp::PacketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe { + &mut UDP_SERVER_TX_DATA[..] + }); + let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); socket .bind(( IpAddress::from(port_forward.destination.ip()), @@ -61,14 +68,14 @@ impl UdpVirtualInterface { fn new_client_socket( source_peer_ip: IpAddr, client_port: VirtualPort, - ) -> anyhow::Result> { - let rx_meta = vec![UdpPacketMetadata::EMPTY; 10]; - let tx_meta = vec![UdpPacketMetadata::EMPTY; 10]; + ) -> anyhow::Result> { + let rx_meta = vec![udp::PacketMetadata::EMPTY; 10]; + let tx_meta = vec![udp::PacketMetadata::EMPTY; 10]; let rx_data = vec![0u8; MAX_PACKET]; let tx_data = vec![0u8; MAX_PACKET]; - let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data); - let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data); - let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); + let udp_rx_buffer = udp::PacketBuffer::new(rx_meta, rx_data); + let udp_tx_buffer = udp::PacketBuffer::new(tx_meta, tx_data); + let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); socket .bind((IpAddress::from(source_peer_ip), client_port.num())) .with_context(|| "UDP virtual client failed to bind")?; @@ -89,20 +96,31 @@ impl UdpVirtualInterface { } #[async_trait] -impl VirtualInterfacePoll for UdpVirtualInterface { - async fn poll_loop(self, device: VirtualIpDevice) -> anyhow::Result<()> { +impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> { + async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { // Create CIDR block for source peer IP + each port forward IP let addresses = self.addresses(); + let config = Config::new(HardwareAddress::Ip); // Create virtual interface (contains smoltcp state machine) - let mut iface = InterfaceBuilder::new(device, vec![]) - .ip_addrs(addresses) - .finalize(); + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + addresses.iter().for_each(|addr| { + ip_addrs.push(*addr).unwrap(); + }); + }); + iface.set_any_ip(true); + + // Maps virtual port to its client socket handle + let mut port_client_handle_map: HashMap = HashMap::new(); // Create virtual server for each port forward for port_forward in self.port_forwards.iter() { let server_socket = UdpVirtualInterface::new_server_socket(*port_forward)?; - iface.add_socket(server_socket); + let handle = self.sockets.add(server_socket); + let virtual_port = + VirtualPort::new(port_forward.destination.port(), port_forward.protocol); + port_client_handle_map.insert(virtual_port, handle); } // The next time to poll the interface. Can be None for instant poll. @@ -111,9 +129,6 @@ impl VirtualInterfacePoll for UdpVirtualInterface { // Bus endpoint to read events let mut endpoint = self.bus.new_endpoint(); - // Maps virtual port to its client socket handle - let mut port_client_handle_map: HashMap = HashMap::new(); - // Data packets to send from a virtual client let mut send_queue: HashMap> = HashMap::new(); @@ -127,16 +142,12 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } => { let loop_start = smoltcp::time::Instant::now(); - match iface.poll(loop_start) { - Ok(processed) if processed => { - trace!("UDP virtual interface polled some packets to be processed"); - } - Err(e) => error!("UDP virtual interface poll error: {:?}", e), - _ => {} + if iface.poll(loop_start, &mut device, &mut self.sockets) { + log::trace!("UDP virtual interface polled some packets to be processed"); } for (virtual_port, client_handle) in port_client_handle_map.iter() { - let client_socket = iface.get_socket::(*client_handle); + let client_socket = self.sockets.get_mut::(*client_handle); if client_socket.can_send() { if let Some(send_queue) = send_queue.get_mut(virtual_port) { let to_transfer = send_queue.pop_front(); @@ -144,7 +155,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { client_socket .send_slice( &data, - (IpAddress::from(port_forward.destination.ip()), port_forward.destination.port()).into(), + UdpMetadata::from(port_forward.destination), ) .unwrap_or_else(|e| { error!( @@ -172,7 +183,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } // The virtual interface determines the next time to poll (this is to reduce unnecessary polls) - next_poll = match iface.poll_delay(loop_start) { + next_poll = match iface.poll_delay(loop_start, &self.sockets) { Some(smoltcp::time::Duration::ZERO) => None, Some(delay) => { trace!("UDP Virtual interface delayed next poll by {}", delay); @@ -190,7 +201,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } else { // Client socket does not exist let client_socket = UdpVirtualInterface::new_client_socket(self.source_peer_ip, virtual_port)?; - let client_handle = iface.add_socket(client_socket); + let client_handle = self.sockets.add(client_socket); // Add handle to map port_client_handle_map.insert(virtual_port, client_handle); diff --git a/src/wg.rs b/src/wg.rs index 14efa23..e1edf01 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -237,7 +237,7 @@ impl WireGuardTunnel { .ok() // Only care if the packet is destined for this tunnel .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip) - .and_then(|packet| match packet.protocol() { + .and_then(|packet| match packet.next_header() { IpProtocol::Tcp => Some(PortProtocol::Tcp), IpProtocol::Udp => Some(PortProtocol::Udp), // Unrecognized protocol, so we cannot determine where to route From 488a0e0807dbcc79289a65f47966d019ce9e6216 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:01:00 -0500 Subject: [PATCH 126/165] remove AnyIP; fix IPv6 virtual addresses --- src/virtual_iface/tcp.rs | 11 +++++++++-- src/virtual_iface/udp.rs | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 24957bd..c6c1b83 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -10,7 +10,7 @@ use smoltcp::{ iface::{Config, Interface, SocketHandle, SocketSet}, socket::tcp, time::Instant, - wire::{HardwareAddress, IpAddress, IpCidr}, + wire::{HardwareAddress, IpAddress, IpCidr, IpVersion}, }; use std::{ collections::{HashMap, HashSet, VecDeque}, @@ -78,7 +78,7 @@ impl<'a> TcpVirtualInterface<'a> { } addresses .into_iter() - .map(|addr| IpCidr::new(addr, 32)) + .map(|addr| IpCidr::new(addr, addr_length(&addr))) .collect() } } @@ -249,3 +249,10 @@ impl VirtualInterfacePoll for TcpVirtualInterface<'_> { } } } + +const fn addr_length(addr: &IpAddress) -> u8 { + match addr.version() { + IpVersion::Ipv4 => 32, + IpVersion::Ipv6 => 128, + } +} diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 8426e4a..9a584b4 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -10,7 +10,7 @@ use smoltcp::{ iface::{Config, Interface, SocketHandle, SocketSet}, socket::udp::{self, UdpMetadata}, time::Instant, - wire::{HardwareAddress, IpAddress, IpCidr}, + wire::{HardwareAddress, IpAddress, IpCidr, IpVersion}, }; use std::{ collections::{HashMap, HashSet, VecDeque}, @@ -90,7 +90,7 @@ impl<'a> UdpVirtualInterface<'a> { } addresses .into_iter() - .map(|addr| IpCidr::new(addr, 32)) + .map(|addr| IpCidr::new(addr, addr_length(&addr))) .collect() } } @@ -219,3 +219,10 @@ impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> { } } } + +const fn addr_length(addr: &IpAddress) -> u8 { + match addr.version() { + IpVersion::Ipv4 => 32, + IpVersion::Ipv6 => 128, + } +} From 32f189e53a5cdc30c90c0dacd9cea33cc7f2e1ec Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:01:35 -0500 Subject: [PATCH 127/165] Revert virtual port for server socket --- src/virtual_iface/tcp.rs | 16 ++++++---------- src/virtual_iface/udp.rs | 16 ++++++---------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index c6c1b83..ff6d575 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -93,22 +93,15 @@ impl VirtualInterfacePoll for TcpVirtualInterface<'_> { // Create virtual interface (contains smoltcp state machine) let mut iface = Interface::new(config, &mut device, Instant::now()); iface.update_ip_addrs(|ip_addrs| { - addresses.iter().for_each(|addr| { - ip_addrs.push(*addr).unwrap(); + addresses.into_iter().for_each(|addr| { + ip_addrs.push(addr).unwrap(); }); }); - iface.set_any_ip(true); - - // Maps virtual port to its client socket handle - let mut port_client_handle_map: HashMap = HashMap::new(); // Create virtual server for each port forward for port_forward in self.port_forwards.iter() { let server_socket = TcpVirtualInterface::new_server_socket(*port_forward)?; - let handle = self.sockets.add(server_socket); - let virtual_port = - VirtualPort::new(port_forward.destination.port(), port_forward.protocol); - port_client_handle_map.insert(virtual_port, handle); + self.sockets.add(server_socket); } // The next time to poll the interface. Can be None for instant poll. @@ -117,6 +110,9 @@ impl VirtualInterfacePoll for TcpVirtualInterface<'_> { // Bus endpoint to read events let mut endpoint = self.bus.new_endpoint(); + // Maps virtual port to its client socket handle + let mut port_client_handle_map: HashMap = HashMap::new(); + // Data packets to send from a virtual client let mut send_queue: HashMap> = HashMap::new(); diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 9a584b4..6730c03 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -105,22 +105,15 @@ impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> { // Create virtual interface (contains smoltcp state machine) let mut iface = Interface::new(config, &mut device, Instant::now()); iface.update_ip_addrs(|ip_addrs| { - addresses.iter().for_each(|addr| { - ip_addrs.push(*addr).unwrap(); + addresses.into_iter().for_each(|addr| { + ip_addrs.push(addr).unwrap(); }); }); - iface.set_any_ip(true); - - // Maps virtual port to its client socket handle - let mut port_client_handle_map: HashMap = HashMap::new(); // Create virtual server for each port forward for port_forward in self.port_forwards.iter() { let server_socket = UdpVirtualInterface::new_server_socket(*port_forward)?; - let handle = self.sockets.add(server_socket); - let virtual_port = - VirtualPort::new(port_forward.destination.port(), port_forward.protocol); - port_client_handle_map.insert(virtual_port, handle); + self.sockets.add(server_socket); } // The next time to poll the interface. Can be None for instant poll. @@ -129,6 +122,9 @@ impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> { // Bus endpoint to read events let mut endpoint = self.bus.new_endpoint(); + // Maps virtual port to its client socket handle + let mut port_client_handle_map: HashMap = HashMap::new(); + // Data packets to send from a virtual client let mut send_queue: HashMap> = HashMap::new(); From 21fe78f54049203c389edde20bbed50d710de3ec Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:44:25 -0500 Subject: [PATCH 128/165] Add docs/example for SOCKS proxy --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index b7e14fa..60b4c15 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,49 @@ if the least recently used port hasn't been used for a certain amount of time. I All in all, I would not recommend using UDP forwarding for public services, since it's most likely prone to simple DoS or DDoS. +## HTTP/SOCKS Proxy + +**onetun** is a Transport-layer proxy (also known as port forwarding); it is not in scope to provide +a HTTP/SOCKS proxy server. However, you can easily chain **onetun** with a proxy server on a remote +that is locked down to your WireGuard network. + +For example, you could run [dante-server](https://www.inet.no/dante/) on a peer (ex. `192.168.4.2`) with the following configuration: + +``` +# /etc/danted.conf + +logoutput: syslog +user.privileged: root +user.unprivileged: nobody + +internal: 192.168.4.2 port=1080 +external: eth0 + +socksmethod: none +clientmethod: none + +# Locks down proxy use to WireGuard peers (192.168.4.x) +client pass { + from: 192.168.4.0/24 to: 0.0.0.0/0 +} +socks pass { + from: 192.168.4.0/24 to: 0.0.0.0/0 +} +``` + +Then use **onetun** to expose the SOCKS5 proxy locally: + +```shell +onetun 127.0.0.1:1080:192.168.4.2:1080 +INFO onetun::tunnel > Tunneling TCP [127.0.0.1:1080]->[192.168.4.2:1080] (via [140.30.3.182:51820] as peer 192.168.4.3) +``` + +Test with `curl` (or configure your browser): + +```shell +curl -x socks5://127.0.0.1:1080 https://ifconfig.me +``` + ## Contributing and Maintenance I will gladly accept contributions to onetun, and set aside time to review all pull-requests. From 83beb48b070ef60cc0dd514f99cb1b16ef5a6ab7 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:59:55 -0500 Subject: [PATCH 129/165] release: v0.3.7 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed1457b..ae74555 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,7 +602,7 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "onetun" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index da35c45..6ed6b46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.6" +version = "0.3.7" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From 10b88ccc60824112232d86eccbc4d75b0f791edd Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 11:23:58 -0500 Subject: [PATCH 130/165] cleanup: SockSet can be owned by static ref: https://github.com/smoltcp-rs/smoltcp/pull/813 --- src/virtual_iface/tcp.rs | 8 ++++---- src/virtual_iface/udp.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index ff6d575..ab9ca08 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -21,14 +21,14 @@ use std::{ const MAX_PACKET: usize = 65536; /// A virtual interface for proxying Layer 7 data to Layer 3 packets, and vice-versa. -pub struct TcpVirtualInterface<'a> { +pub struct TcpVirtualInterface { source_peer_ip: IpAddr, port_forwards: Vec, bus: Bus, - sockets: SocketSet<'a>, + sockets: SocketSet<'static>, } -impl<'a> TcpVirtualInterface<'a> { +impl TcpVirtualInterface { /// Initialize the parameters for a new virtual interface. /// Use the `poll_loop()` future to start the virtual interface poll loop. pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { @@ -84,7 +84,7 @@ impl<'a> TcpVirtualInterface<'a> { } #[async_trait] -impl VirtualInterfacePoll for TcpVirtualInterface<'_> { +impl VirtualInterfacePoll for TcpVirtualInterface { async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { // Create CIDR block for source peer IP + each port forward IP let addresses = self.addresses(); diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 6730c03..214c01e 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -20,14 +20,14 @@ use std::{ const MAX_PACKET: usize = 65536; -pub struct UdpVirtualInterface<'a> { +pub struct UdpVirtualInterface { source_peer_ip: IpAddr, port_forwards: Vec, bus: Bus, - sockets: SocketSet<'a>, + sockets: SocketSet<'static>, } -impl<'a> UdpVirtualInterface<'a> { +impl UdpVirtualInterface { /// Initialize the parameters for a new virtual interface. /// Use the `poll_loop()` future to start the virtual interface poll loop. pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { @@ -96,7 +96,7 @@ impl<'a> UdpVirtualInterface<'a> { } #[async_trait] -impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> { +impl VirtualInterfacePoll for UdpVirtualInterface { async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { // Create CIDR block for source peer IP + each port forward IP let addresses = self.addresses(); From 72ab679142944d3ba7731ef69df865a334526559 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 11:28:15 -0500 Subject: [PATCH 131/165] update to smoltcp 0.11 --- Cargo.lock | 56 +++++++----------------------------------------------- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae74555..3ae8171 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,15 +60,6 @@ dependencies = [ "syn 2.0.41", ] -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "atty" version = "0.2.14" @@ -213,12 +204,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "critical-section" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" - [[package]] name = "defmt" version = "0.3.5" @@ -382,9 +367,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hash32" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] @@ -397,14 +382,11 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heapless" -version = "0.7.17" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "atomic-polyfill", "hash32", - "rustc_version", - "spin 0.9.8", "stable_deref_trait", ] @@ -808,7 +790,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin 0.5.2", + "spin", "untrusted 0.7.1", "web-sys", "winapi", @@ -820,15 +802,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.28" @@ -857,12 +830,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" - [[package]] name = "slab" version = "0.4.9" @@ -880,9 +847,9 @@ checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smoltcp" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2e3a36ac8fea7b94e666dfa3871063d6e0a5c9d5d4fec9a1a6b7b6760f0229" +checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" dependencies = [ "bitflags 1.3.2", "byteorder", @@ -909,15 +876,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 6ed6b46..06f34af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ rand = "0.8" nom = "7" async-trait = "0.1" priority-queue = "1.3" -smoltcp = { version = "0.10", default-features = false, features = [ +smoltcp = { version = "0.11", default-features = false, features = [ "std", "log", "medium-ip", From 0931ed496a194079ecbdb3a27e6b1c83e47b07bf Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 11:51:28 -0500 Subject: [PATCH 132/165] update boringtun to 0.6.0 --- Cargo.lock | 338 ++++++++++++++++++++++++++++++++++++++++++++--------- Cargo.toml | 2 +- 2 files changed, 285 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ae8171..36eb0b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -111,21 +121,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] -name = "boringtun" -version = "0.4.0" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcaf3e6d388237249ec234ec6890bfc68634043f9b048011de8d0fc0025c3698" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "boringtun" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "751787b019c674b9ac353f4eaa285e6711c21badb421cd8c199bf2c83b727f29" +dependencies = [ + "aead", "base64", + "blake2", + "chacha20poly1305", "hex", + "hmac", "ip_network", "ip_network_table", - "jni", "libc", + "nix", "parking_lot", + "rand_core", "ring", "tracing", "untrusted 0.9.0", + "x25519-dalek", ] [[package]] @@ -155,18 +189,47 @@ dependencies = [ "libc", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "4.4.11" @@ -195,13 +258,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] -name = "combine" -version = "4.6.6" +name = "cpufeatures" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ - "bytes", - "memchr", + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", ] [[package]] @@ -236,6 +336,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -259,6 +370,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + [[package]] name = "futures" version = "0.3.29" @@ -348,6 +465,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -411,6 +538,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "humantime" version = "1.3.0" @@ -430,6 +566,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "ip_network" version = "0.4.1" @@ -452,26 +597,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" -[[package]] -name = "jni" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "js-sys" version = "0.3.66" @@ -547,6 +672,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -604,6 +741,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "parking_lot" version = "0.12.1" @@ -639,6 +782,23 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "platforms" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -802,6 +962,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.28" @@ -815,21 +984,38 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] + [[package]] name = "slab" version = "0.4.9" @@ -888,6 +1074,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1008,12 +1200,28 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -1032,16 +1240,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "walkdir" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1274,3 +1472,35 @@ name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "x25519-dalek" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] diff --git a/Cargo.toml b/Cargo.toml index 06f34af..6683f29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/aramperes/onetun" [dependencies] # Required dependencies (bin and lib) -boringtun = { version = "0.4.0", default-features = false } +boringtun = { version = "0.6.0", default-features = false } log = "0.4" anyhow = "1" tokio = { version = "1", features = [ "rt", "sync", "io-util", "net", "time", "fs", "macros" ] } From e23cfc3e7ea1f6356d156d49e7e965e51f244d80 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 11:52:07 -0500 Subject: [PATCH 133/165] Update to new x25519 primitives --- src/config.rs | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/config.rs b/src/config.rs index 11425da..4c030dd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,18 +5,18 @@ use std::fs::read_to_string; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::sync::Arc; -use anyhow::Context; -pub use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; +use anyhow::{bail, Context}; +pub use boringtun::x25519::{PublicKey, StaticSecret}; const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Config { pub port_forwards: Vec, #[allow(dead_code)] pub remote_port_forwards: Vec, - pub private_key: Arc, - pub endpoint_public_key: Arc, + pub private_key: Arc, + pub endpoint_public_key: Arc, pub preshared_key: Option<[u8; 32]>, pub endpoint_addr: SocketAddr, pub endpoint_bind_addr: SocketAddr, @@ -305,24 +305,33 @@ fn parse_ip(s: Option<&String>) -> anyhow::Result { .with_context(|| "Invalid IP address") } -fn parse_private_key(s: &str) -> anyhow::Result { - s.parse::() - .map_err(|e| anyhow::anyhow!("{}", e)) +fn parse_private_key(s: &str) -> anyhow::Result { + let decoded = base64::decode(s).with_context(|| "Failed to decode private key")?; + if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { + Ok(StaticSecret::from(bytes)) + } else { + bail!("Invalid private key") + } } -fn parse_public_key(s: Option<&String>) -> anyhow::Result { - s.with_context(|| "Missing public key")? - .parse::() - .map_err(|e| anyhow::anyhow!("{}", e)) - .with_context(|| "Invalid public key") +fn parse_public_key(s: Option<&String>) -> anyhow::Result { + let encoded = s.with_context(|| "Missing public key")?; + let decoded = base64::decode(encoded).with_context(|| "Failed to decode public key")?; + if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { + Ok(PublicKey::from(bytes)) + } else { + bail!("Invalid public key") + } } fn parse_preshared_key(s: Option<&String>) -> anyhow::Result> { if let Some(s) = s { - let psk = base64::decode(s).with_context(|| "Invalid pre-shared key")?; - Ok(Some(psk.try_into().map_err(|_| { - anyhow::anyhow!("Unsupported pre-shared key") - })?)) + let decoded = base64::decode(s).with_context(|| "Failed to decode preshared key")?; + if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { + Ok(Some(bytes)) + } else { + bail!("Invalid preshared key") + } } else { Ok(None) } From 1d703facc0ab323270a842b2a4021a7eeb06d16f Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 14:42:34 -0500 Subject: [PATCH 134/165] Implement locking of Tunn in WireGuardTunnel --- src/wg.rs | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/wg.rs b/src/wg.rs index e1edf01..5f5b735 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -9,6 +9,7 @@ use boringtun::noise::{Tunn, TunnResult}; use log::Level; use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet}; use tokio::net::UdpSocket; +use tokio::sync::Mutex; use crate::config::{Config, PortProtocol}; use crate::events::Event; @@ -23,7 +24,7 @@ const MAX_PACKET: usize = 65536; pub struct WireGuardTunnel { pub(crate) source_peer_ip: IpAddr, /// `boringtun` peer/tunnel implementation, used for crypto & WG protocol. - peer: Box, + peer: Mutex>, /// The UDP socket for the public WireGuard endpoint to connect to. udp: UdpSocket, /// The address of the public WireGuard endpoint (UDP). @@ -36,7 +37,7 @@ impl WireGuardTunnel { /// Initialize a new WireGuard tunnel. pub async fn new(config: &Config, bus: Bus) -> anyhow::Result { let source_peer_ip = config.source_peer_ip; - let peer = Self::create_tunnel(config)?; + let peer = Mutex::new(Box::new(Self::create_tunnel(config)?)); let endpoint = config.endpoint_addr; let udp = UdpSocket::bind(config.endpoint_bind_addr) .await @@ -55,7 +56,11 @@ impl WireGuardTunnel { pub async fn send_ip_packet(&self, packet: &[u8]) -> anyhow::Result<()> { trace_ip_packet("Sending IP packet", packet); let mut send_buf = [0u8; MAX_PACKET]; - match self.peer.encapsulate(packet, &mut send_buf) { + let encapsulate_result = { + let mut peer = self.peer.lock().await; + peer.encapsulate(packet, &mut send_buf) + }; + match encapsulate_result { TunnResult::WriteToNetwork(packet) => { self.udp .send_to(packet, self.endpoint) @@ -104,7 +109,7 @@ impl WireGuardTunnel { loop { let mut send_buf = [0u8; MAX_PACKET]; - let tun_result = self.peer.update_timers(&mut send_buf); + let tun_result = { self.peer.lock().await.update_timers(&mut send_buf) }; self.handle_routine_tun_result(tun_result).await; } } @@ -131,7 +136,11 @@ impl WireGuardTunnel { warn!("Wireguard handshake has expired!"); let mut buf = vec![0u8; MAX_PACKET]; - let result = self.peer.format_handshake_initiation(&mut buf[..], false); + let result = self + .peer + .lock() + .await + .format_handshake_initiation(&mut buf[..], false); self.handle_routine_tun_result(result).await } @@ -172,7 +181,11 @@ impl WireGuardTunnel { }; let data = &recv_buf[..size]; - match self.peer.decapsulate(None, data, &mut send_buf) { + let decapsulate_result = { + let mut peer = self.peer.lock().await; + peer.decapsulate(None, data, &mut send_buf) + }; + match decapsulate_result { TunnResult::WriteToNetwork(packet) => { match self.udp.send_to(packet, self.endpoint).await { Ok(_) => {} @@ -181,9 +194,10 @@ impl WireGuardTunnel { continue; } }; + let mut peer = self.peer.lock().await; loop { let mut send_buf = [0u8; MAX_PACKET]; - match self.peer.decapsulate(None, &[], &mut send_buf) { + match peer.decapsulate(None, &[], &mut send_buf) { TunnResult::WriteToNetwork(packet) => { match self.udp.send_to(packet, self.endpoint).await { Ok(_) => {} @@ -217,10 +231,13 @@ impl WireGuardTunnel { } } - fn create_tunnel(config: &Config) -> anyhow::Result> { + fn create_tunnel(config: &Config) -> anyhow::Result { + let private = config.private_key.as_ref().clone(); + let public = *config.endpoint_public_key.as_ref(); + Tunn::new( - config.private_key.clone(), - config.endpoint_public_key.clone(), + private, + public, config.preshared_key, config.keepalive_seconds, 0, From 3ccd000ea8ad1e81d6abe52d43096e8e29a24270 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 14:58:51 -0500 Subject: [PATCH 135/165] Minor dependency updates --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36eb0b8..d67b4de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" [[package]] name = "async-recursion" @@ -1142,9 +1142,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.35.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", From ce40f85efa502f0779eddec23e424c753a7c5e02 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 15:05:33 -0500 Subject: [PATCH 136/165] Cleanup usage of anyhow with_context --- src/config.rs | 70 +++++++++++++++++----------------------- src/lib.rs | 2 +- src/main.rs | 6 ++-- src/pcap.rs | 20 ++++++------ src/tunnel/tcp.rs | 6 ++-- src/tunnel/udp.rs | 6 ++-- src/virtual_iface/tcp.rs | 4 +-- src/virtual_iface/udp.rs | 4 +-- src/wg.rs | 6 ++-- 9 files changed, 55 insertions(+), 69 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4c030dd..f3fdbc4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -161,14 +161,14 @@ impl Config { .map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE)) .collect(); let port_forwards: Vec = port_forwards - .with_context(|| "Failed to parse port forward config")? + .context("Failed to parse port forward config")? .into_iter() .flatten() .collect(); // Read source-peer-ip let source_peer_ip = parse_ip(matches.get_one::("source-peer-ip")) - .with_context(|| "Invalid source peer IP")?; + .context("Invalid source peer IP")?; // Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs let mut port_forward_strings = HashSet::new(); @@ -196,7 +196,7 @@ impl Config { }) .collect(); let mut remote_port_forwards: Vec = remote_port_forwards - .with_context(|| "Failed to parse remote port forward config")? + .context("Failed to parse remote port forward config")? .into_iter() .flatten() .collect(); @@ -229,7 +229,7 @@ impl Config { { read_to_string(private_key_file) .map(|s| s.trim().to_string()) - .with_context(|| "Failed to read private key file") + .context("Failed to read private key file") } else { if std::env::var("ONETUN_PRIVATE_KEY").is_err() { warnings.push("Private key was passed using CLI. This is insecure. \ @@ -238,20 +238,18 @@ impl Config { matches .get_one::("private-key") .cloned() - .with_context(|| "Missing private key") + .context("Missing private key") }?; let endpoint_addr = parse_addr(matches.get_one::("endpoint-addr")) - .with_context(|| "Invalid endpoint address")?; + .context("Invalid endpoint address")?; let endpoint_bind_addr = if let Some(addr) = matches.get_one::("endpoint-bind-addr") { - let addr = parse_addr(Some(addr)).with_context(|| "Invalid bind address")?; + let addr = parse_addr(Some(addr)).context("Invalid bind address")?; // Make sure the bind address and endpoint address are the same IP version if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() { - return Err(anyhow::anyhow!( - "Endpoint and bind addresses must be the same IP version" - )); + bail!("Endpoint and bind addresses must be the same IP version"); } addr } else { @@ -265,21 +263,19 @@ impl Config { Ok(Self { port_forwards, remote_port_forwards, - private_key: Arc::new( - parse_private_key(&private_key).with_context(|| "Invalid private key")?, - ), + private_key: Arc::new(parse_private_key(&private_key).context("Invalid private key")?), endpoint_public_key: Arc::new( parse_public_key(matches.get_one::("endpoint-public-key")) - .with_context(|| "Invalid endpoint public key")?, + .context("Invalid endpoint public key")?, ), preshared_key: parse_preshared_key(matches.get_one::("preshared-key"))?, endpoint_addr, endpoint_bind_addr, source_peer_ip, keepalive_seconds: parse_keep_alive(matches.get_one::("keep-alive")) - .with_context(|| "Invalid keep-alive value")?, + .context("Invalid keep-alive value")?, max_transmission_unit: parse_mtu(matches.get_one::("max-transmission-unit")) - .with_context(|| "Invalid max-transmission-unit value")?, + .context("Invalid max-transmission-unit value")?, log: matches .get_one::("log") .cloned() @@ -291,22 +287,22 @@ impl Config { } fn parse_addr>(s: Option) -> anyhow::Result { - s.with_context(|| "Missing address")? + s.context("Missing address")? .as_ref() .to_socket_addrs() - .with_context(|| "Invalid address")? + .context("Invalid address")? .next() - .with_context(|| "Could not lookup address") + .context("Could not lookup address") } fn parse_ip(s: Option<&String>) -> anyhow::Result { - s.with_context(|| "Missing IP")? + s.context("Missing IP address")? .parse::() - .with_context(|| "Invalid IP address") + .context("Invalid IP address") } fn parse_private_key(s: &str) -> anyhow::Result { - let decoded = base64::decode(s).with_context(|| "Failed to decode private key")?; + let decoded = base64::decode(s).context("Failed to decode private key")?; if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { Ok(StaticSecret::from(bytes)) } else { @@ -315,8 +311,8 @@ fn parse_private_key(s: &str) -> anyhow::Result { } fn parse_public_key(s: Option<&String>) -> anyhow::Result { - let encoded = s.with_context(|| "Missing public key")?; - let decoded = base64::decode(encoded).with_context(|| "Failed to decode public key")?; + let encoded = s.context("Missing public key")?; + let decoded = base64::decode(encoded).context("Failed to decode public key")?; if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { Ok(PublicKey::from(bytes)) } else { @@ -326,7 +322,7 @@ fn parse_public_key(s: Option<&String>) -> anyhow::Result { fn parse_preshared_key(s: Option<&String>) -> anyhow::Result> { if let Some(s) = s { - let decoded = base64::decode(s).with_context(|| "Failed to decode preshared key")?; + let decoded = base64::decode(s).context("Failed to decode preshared key")?; if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { Ok(Some(bytes)) } else { @@ -352,9 +348,7 @@ fn parse_keep_alive(s: Option<&String>) -> anyhow::Result> { } fn parse_mtu(s: Option<&String>) -> anyhow::Result { - s.with_context(|| "Missing MTU")? - .parse() - .with_context(|| "Invalid MTU") + s.context("Missing MTU")?.parse().context("Invalid MTU") } #[cfg(unix)] @@ -483,27 +477,21 @@ impl PortForwardConfig { let source = ( src_addr.0.unwrap_or(default_source), - src_addr - .1 - .parse::() - .with_context(|| "Invalid source port")?, + src_addr.1.parse::().context("Invalid source port")?, ) .to_socket_addrs() - .with_context(|| "Invalid source address")? + .context("Invalid source address")? .next() - .with_context(|| "Could not resolve source address")?; + .context("Could not resolve source address")?; let destination = ( dst_addr.0, - dst_addr - .1 - .parse::() - .with_context(|| "Invalid source port")?, + dst_addr.1.parse::().context("Invalid source port")?, ) .to_socket_addrs() // TODO: Pass this as given and use DNS config instead (issue #15) - .with_context(|| "Invalid destination address")? + .context("Invalid destination address")? .next() - .with_context(|| "Could not resolve destination address")?; + .context("Could not resolve destination address")?; // Parse protocols let protocols = if let Some(protocols) = protocols { @@ -513,7 +501,7 @@ impl PortForwardConfig { } else { Ok(vec![PortProtocol::Tcp]) } - .with_context(|| "Failed to parse protocols")?; + .context("Failed to parse protocols")?; // Returns an config for each protocol Ok(protocols diff --git a/src/lib.rs b/src/lib.rs index a43d657..a76fa18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> { let wg = WireGuardTunnel::new(&config, bus.clone()) .await - .with_context(|| "Failed to initialize WireGuard tunnel")?; + .context("Failed to initialize WireGuard tunnel")?; let wg = Arc::new(wg); { diff --git a/src/main.rs b/src/main.rs index 78c6419..4ff2954 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ async fn main() -> anyhow::Result<()> { use anyhow::Context; use onetun::{config::Config, events::Bus}; - let config = Config::from_args().with_context(|| "Failed to read config")?; + let config = Config::from_args().context("Configuration has errors")?; init_logger(&config)?; for warning in &config.warnings { @@ -32,7 +32,5 @@ fn init_logger(config: &onetun::config::Config) -> anyhow::Result<()> { let mut builder = pretty_env_logger::formatted_timed_builder(); builder.parse_filters(&config.log); - builder - .try_init() - .with_context(|| "Failed to initialize logger") + builder.try_init().context("Failed to initialize logger") } diff --git a/src/pcap.rs b/src/pcap.rs index 1771a33..487fbe0 100644 --- a/src/pcap.rs +++ b/src/pcap.rs @@ -16,7 +16,7 @@ impl Pcap { self.writer .flush() .await - .with_context(|| "Failed to flush pcap writer") + .context("Failed to flush pcap writer") } async fn write(&mut self, data: &[u8]) -> anyhow::Result { @@ -30,14 +30,14 @@ impl Pcap { self.writer .write_u16(value) .await - .with_context(|| "Failed to write u16 to pcap writer") + .context("Failed to write u16 to pcap writer") } async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> { self.writer .write_u32(value) .await - .with_context(|| "Failed to write u32 to pcap writer") + .context("Failed to write u32 to pcap writer") } async fn global_header(&mut self) -> anyhow::Result<()> { @@ -64,14 +64,14 @@ impl Pcap { async fn packet(&mut self, timestamp: Instant, packet: &[u8]) -> anyhow::Result<()> { self.packet_header(timestamp, packet.len()) .await - .with_context(|| "Failed to write packet header to pcap writer")?; + .context("Failed to write packet header to pcap writer")?; self.write(packet) .await - .with_context(|| "Failed to write packet to pcap writer")?; + .context("Failed to write packet to pcap writer")?; self.writer .flush() .await - .with_context(|| "Failed to flush pcap writer")?; + .context("Failed to flush pcap writer")?; self.flush().await } } @@ -81,14 +81,14 @@ pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> { let mut endpoint = bus.new_endpoint(); let file = File::create(&pcap_file) .await - .with_context(|| "Failed to create pcap file")?; + .context("Failed to create pcap file")?; let writer = BufWriter::new(file); let mut writer = Pcap { writer }; writer .global_header() .await - .with_context(|| "Failed to write global header to pcap writer")?; + .context("Failed to write global header to pcap writer")?; info!("Capturing WireGuard IP packets to {}", &pcap_file); loop { @@ -98,14 +98,14 @@ pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> { writer .packet(instant, &ip) .await - .with_context(|| "Failed to write inbound IP packet to pcap writer")?; + .context("Failed to write inbound IP packet to pcap writer")?; } Event::OutboundInternetPacket(ip) => { let instant = Instant::now(); writer .packet(instant, &ip) .await - .with_context(|| "Failed to write output IP packet to pcap writer")?; + .context("Failed to write output IP packet to pcap writer")?; } _ => {} } diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs index b5e1ec5..47b0197 100644 --- a/src/tunnel/tcp.rs +++ b/src/tunnel/tcp.rs @@ -27,14 +27,14 @@ pub async fn tcp_proxy_server( ) -> anyhow::Result<()> { let listener = TcpListener::bind(port_forward.source) .await - .with_context(|| "Failed to listen on TCP proxy server")?; + .context("Failed to listen on TCP proxy server")?; loop { let port_pool = port_pool.clone(); let (socket, peer_addr) = listener .accept() .await - .with_context(|| "Failed to accept connection on TCP proxy server")?; + .context("Failed to accept connection on TCP proxy server")?; // Assign a 'virtual port': this is a unique port number used to route IP packets // received from the WireGuard tunnel. It is the port number that the virtual client will @@ -192,7 +192,7 @@ impl TcpPortPool { let port = inner .queue .pop_front() - .with_context(|| "TCP virtual port pool is exhausted")?; + .context("TCP virtual port pool is exhausted")?; Ok(VirtualPort::new(port, PortProtocol::Tcp)) } diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs index 32fef15..ab52dc7 100644 --- a/src/tunnel/udp.rs +++ b/src/tunnel/udp.rs @@ -37,7 +37,7 @@ pub async fn udp_proxy_server( let mut endpoint = bus.new_endpoint(); let socket = UdpSocket::bind(port_forward.source) .await - .with_context(|| "Failed to bind on UDP proxy address")?; + .context("Failed to bind on UDP proxy address")?; let mut buffer = [0u8; MAX_PACKET]; loop { @@ -103,7 +103,7 @@ async fn next_udp_datagram( let (size, peer_addr) = socket .recv_from(buffer) .await - .with_context(|| "Failed to accept incoming UDP datagram")?; + .context("Failed to accept incoming UDP datagram")?; // Assign a 'virtual port': this is a unique port number used to route IP packets // received from the WireGuard tunnel. It is the port number that the virtual client will @@ -212,7 +212,7 @@ impl UdpPortPool { None } }) - .with_context(|| "virtual port pool is exhausted")?; + .context("Virtual port pool is exhausted")?; inner.port_by_peer_addr.insert(peer_addr, port); inner.peer_addr_by_port.insert(port, peer_addr); diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index ab9ca08..7522d7e 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -56,7 +56,7 @@ impl TcpVirtualInterface { IpAddress::from(port_forward.destination.ip()), port_forward.destination.port(), )) - .with_context(|| "Virtual server socket failed to listen")?; + .context("Virtual server socket failed to listen")?; Ok(socket) } @@ -218,7 +218,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { ), (IpAddress::from(self.source_peer_ip), virtual_port.num()), ) - .with_context(|| "Virtual server socket failed to listen")?; + .context("Virtual server socket failed to listen")?; next_poll = None; } diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 214c01e..3ca4c2d 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -61,7 +61,7 @@ impl UdpVirtualInterface { IpAddress::from(port_forward.destination.ip()), port_forward.destination.port(), )) - .with_context(|| "UDP virtual server socket failed to bind")?; + .context("UDP virtual server socket failed to bind")?; Ok(socket) } @@ -78,7 +78,7 @@ impl UdpVirtualInterface { let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); socket .bind((IpAddress::from(source_peer_ip), client_port.num())) - .with_context(|| "UDP virtual client failed to bind")?; + .context("UDP virtual client failed to bind")?; Ok(socket) } diff --git a/src/wg.rs b/src/wg.rs index 5f5b735..fc346a2 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -41,7 +41,7 @@ impl WireGuardTunnel { let endpoint = config.endpoint_addr; let udp = UdpSocket::bind(config.endpoint_bind_addr) .await - .with_context(|| "Failed to create UDP socket for WireGuard connection")?; + .context("Failed to create UDP socket for WireGuard connection")?; Ok(Self { source_peer_ip, @@ -65,7 +65,7 @@ impl WireGuardTunnel { self.udp .send_to(packet, self.endpoint) .await - .with_context(|| "Failed to send encrypted IP packet to WireGuard endpoint.")?; + .context("Failed to send encrypted IP packet to WireGuard endpoint.")?; debug!( "Sent {} bytes to WireGuard endpoint (encrypted IP packet)", packet.len() @@ -244,7 +244,7 @@ impl WireGuardTunnel { None, ) .map_err(|s| anyhow::anyhow!("{}", s)) - .with_context(|| "Failed to initialize boringtun Tunn") + .context("Failed to initialize boringtun Tunn") } /// Determine the inner protocol of the incoming IP packet (TCP/UDP). From 56c950d159cea141b0122267f4f37b5eadba88cc Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 24 Dec 2023 15:23:12 -0500 Subject: [PATCH 137/165] Use bail when possible --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index f3fdbc4..411efa3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -202,14 +202,14 @@ impl Config { .collect(); for port_forward in remote_port_forwards.iter_mut() { if port_forward.source.ip() != source_peer_ip { - return Err(anyhow::anyhow!("Remote port forward config must match --source-peer-ip ({}), or be omitted.", source_peer_ip)); + bail!("Remote port forward config must match --source-peer-ip ({}), or be omitted.", source_peer_ip); } port_forward.source = SocketAddr::from((source_peer_ip, port_forward.source.port())); port_forward.remote = true; } if port_forwards.is_empty() && remote_port_forwards.is_empty() { - return Err(anyhow::anyhow!("No port forward configurations given.")); + bail!("No port forward configurations given."); } // Read private key from file or CLI argument From 2b6d21572e39ded7488f1e592a08192dfa458c06 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Sun, 7 Apr 2024 01:32:37 +0800 Subject: [PATCH 138/165] Optimize apt-get commands to reduce image size in Dockerfile This commit improves the Dockerfile by consolidating apt-get update and apt-get install commands into a single RUN statement and adding cleanup steps for the apt cache. --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 59ad657..b8087a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,9 @@ COPY . . RUN cargo build --release FROM debian:11-slim -RUN apt-get update -RUN apt-get install dumb-init -y +RUN apt-get update \ + && apt-get install dumb-init -y \ + && rm -rf /var/lib/apt/lists/* COPY --from=cargo-build /usr/src/onetun/target/release/onetun /usr/local/bin/onetun From c86784ed70f158f310480eb1b1a71c0a09ab8d5d Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 11:33:53 -0500 Subject: [PATCH 139/165] log a better error regarding smoltcp max interface limit --- src/virtual_iface/tcp.rs | 4 +++- src/virtual_iface/udp.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 7522d7e..39d45ac 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -94,7 +94,9 @@ impl VirtualInterfacePoll for TcpVirtualInterface { let mut iface = Interface::new(config, &mut device, Instant::now()); iface.update_ip_addrs(|ip_addrs| { addresses.into_iter().for_each(|addr| { - ip_addrs.push(addr).unwrap(); + ip_addrs + .push(addr) + .expect("maximum number of IPs in TCP interface reached"); }); }); diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index 3ca4c2d..c72c51c 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -106,7 +106,9 @@ impl VirtualInterfacePoll for UdpVirtualInterface { let mut iface = Interface::new(config, &mut device, Instant::now()); iface.update_ip_addrs(|ip_addrs| { addresses.into_iter().for_each(|addr| { - ip_addrs.push(addr).unwrap(); + ip_addrs + .push(addr) + .expect("maximum number of IPs in UDP interface reached"); }); }); From 9ccd2e19f6f680a77de3e56a3e4549bf8cdc820f Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:03:41 -0500 Subject: [PATCH 140/165] increase default smoltcp interface limit and add to README --- .cargo/config.toml | 4 ++++ README.md | 8 ++++++++ 2 files changed, 12 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..408c0f3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +[env] +# Each interface needs 1 IP allocated to the WireGuard peer IP. +# "8" = 7 tunnels per protocol. +SMOLTCP_IFACE_MAX_ADDR_COUNT = "8" diff --git a/README.md b/README.md index 60b4c15..413e6d0 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,14 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [ ... would open TCP ports 8080 and 8081 locally, which forward to their respective ports on the different peers. +#### Maximum number of tunnels + +`smoltcp` imposes a compile-time limit on the number of IP addresses assigned to an interface. **onetun** increases +the default value to support most use-cases. In effect, the default limit on the number of **onetun** peers +is **7 per protocol** (TCP and UDP). + +Should you need more unique IP addresses to forward ports to, you can increase the limit in `.cargo/config.toml` and recompile **onetun**. + ### UDP Support **onetun** supports UDP forwarding. You can add `:UDP` at the end of the port-forward configuration, or `UDP,TCP` to support From 88ce1245444334898f8351e7c9e710308a0be803 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:03:51 -0500 Subject: [PATCH 141/165] formatting --- src/virtual_device.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 7af4d27..febe2ac 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -55,8 +55,14 @@ impl VirtualIpDevice { } impl smoltcp::phy::Device for VirtualIpDevice { - type RxToken<'a> = RxToken where Self: 'a; - type TxToken<'a> = TxToken where Self: 'a; + type RxToken<'a> + = RxToken + where + Self: 'a; + type TxToken<'a> + = TxToken + where + Self: 'a; fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { let next = { From 06049161abe8e609165538fe8ab5cb933d7a66ce Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:27:41 -0500 Subject: [PATCH 142/165] bump MSRV to 1.74.0 --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 2 +- Dockerfile | 2 +- README.md | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c85326b..a5f79a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.70.0 + - 1.74.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.70.0 + - 1.74.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd4b468..5192d89 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: run: echo "${{ env.VERSION }}" > artifacts/release-version - name: Upload artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: artifacts path: artifacts diff --git a/Dockerfile b/Dockerfile index b8087a9..506b591 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.70.0 as cargo-build +FROM rust:1.83.0 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml diff --git a/README.md b/README.md index 413e6d0..a47e3c2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.70.0: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.74.0: ```shell cargo install onetun @@ -37,7 +37,7 @@ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.70.0: +You can also build onetun locally, using Rust ≥1.74.0: ```shell git clone https://github.com/aramperes/onetun && cd onetun From 1f3d9f035ff350e3c46116e4c278f19836d213d7 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:28:13 -0500 Subject: [PATCH 143/165] release: v0.3.8 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d67b4de..de762b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -721,7 +721,7 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "onetun" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 6683f29..2602c36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.7" +version = "0.3.8" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From 4fa83047996c13eed7e69af9a1f19d4ef48a7ee0 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:30:13 -0500 Subject: [PATCH 144/165] bump MSRV to 1.78.0 --- .github/workflows/build.yml | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5f79a0..9968a9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.74.0 + - 1.78.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.74.0 + - 1.78.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/README.md b/README.md index a47e3c2..28db5ef 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.74.0: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.78.0: ```shell cargo install onetun @@ -37,7 +37,7 @@ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.74.0: +You can also build onetun locally, using Rust ≥1.78.0: ```shell git clone https://github.com/aramperes/onetun && cd onetun From f3661c0a2cfc1862d24557a8e729a7162c4726e6 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:33:56 -0500 Subject: [PATCH 145/165] fix docker build --- .github/workflows/release.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5192d89..f898a85 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,7 +113,7 @@ jobs: target: ${{ matrix.target }} - name: Get release download URL - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: artifacts path: artifacts diff --git a/Dockerfile b/Dockerfile index 506b591..90f851d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.83.0 as cargo-build +FROM rust:1.82.0 as cargo-build WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml From 784ab97c8bbed2fae0aca14b5f67911264897f8e Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:41:23 -0500 Subject: [PATCH 146/165] release: v0.3.9; add macos-aarch64 build --- .github/workflows/release.yml | 6 +++--- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f898a85..3e70aa8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,16 +75,16 @@ jobs: RUST_BACKTRACE: 1 strategy: matrix: - build: [ linux-amd64, macos-intel, windows ] + build: [ linux-amd64, macos-aarch64, windows ] include: - build: linux-amd64 os: ubuntu-latest rust: stable target: x86_64-unknown-linux-musl - - build: macos-intel + - build: macos-aarch64 os: macos-latest rust: stable - target: x86_64-apple-darwin + target: aarch64-apple-darwin - build: windows os: windows-2019 rust: stable diff --git a/Cargo.lock b/Cargo.lock index de762b5..c82ace3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,7 +721,7 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "onetun" -version = "0.3.8" +version = "0.3.9" dependencies = [ "anyhow", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 2602c36..a318177 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.8" +version = "0.3.9" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From ca3590a4c0f4f7fd5738dd0c2c2a0e6861c720d1 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:13:46 -0500 Subject: [PATCH 147/165] chore: bump minor dependencies --- Cargo.lock | 597 ++++++++++++++++++++++++----------------------------- 1 file changed, 275 insertions(+), 322 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c82ace3..a42eb07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -29,45 +29,45 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" -version = "1.0.76" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] @@ -76,30 +76,30 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -116,9 +116,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake2" @@ -164,9 +164,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -176,17 +176,17 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -232,18 +232,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.11" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstyle", "clap_lex", @@ -253,15 +253,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -301,14 +301,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] name = "defmt" -version = "0.3.5" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" +checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130" dependencies = [ "bitflags 1.3.2", "defmt-macros", @@ -316,22 +316,22 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" +checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6" dependencies = [ "defmt-parser", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] name = "defmt-parser" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" dependencies = [ "thiserror", ] @@ -362,12 +362,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -378,9 +378,9 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "futures" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -403,15 +403,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -420,38 +420,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -477,9 +477,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -488,9 +488,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hash32" @@ -526,12 +526,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - [[package]] name = "hex" version = "0.4.3" @@ -599,30 +593,31 @@ checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.151" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -630,9 +625,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "managed" @@ -642,9 +637,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -654,22 +649,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.10" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -694,30 +689,20 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.3", - "libc", -] - [[package]] name = "object" -version = "0.32.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "onetun" @@ -743,15 +728,15 @@ dependencies = [ [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -759,22 +744,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -784,9 +769,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "platforms" -version = "3.2.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +checksum = "d43467300237085a4f9e864b937cf0bc012cef7740be12be1a48b10d2c8a3701" [[package]] name = "poly1305" @@ -801,9 +786,12 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "pretty_env_logger" @@ -817,43 +805,41 @@ dependencies = [ [[package]] name = "priority-queue" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61" +checksum = "a0bda9164fe05bc9225752d54aae413343c36f684380005398a6a8fde95fe785" dependencies = [ "autocfg", "indexmap", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -866,9 +852,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -905,18 +891,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -926,9 +912,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -937,9 +923,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" @@ -958,26 +944,26 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -992,30 +978,36 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.9" @@ -1027,9 +1019,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smoltcp" @@ -1048,12 +1040,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1070,31 +1062,21 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1103,76 +1085,75 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] name = "tokio" -version = "1.35.1" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -1182,20 +1163,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -1208,9 +1189,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "universal-hash" @@ -1236,9 +1217,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1248,34 +1229,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1283,28 +1265,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -1328,11 +1310,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -1341,137 +1323,87 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" -dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_gnu" -version = "0.52.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "x25519-dalek" @@ -1486,10 +1418,31 @@ dependencies = [ ] [[package]] -name = "zeroize" -version = "1.7.0" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -1502,5 +1455,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn", ] From 0e93a6435a12a1ce6e683e9a788c038385d8b0dc Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:22:37 -0500 Subject: [PATCH 148/165] chore: udpate to smoltcp 0.12 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/virtual_device.rs | 6 +++--- src/virtual_iface/tcp.rs | 3 ++- src/virtual_iface/udp.rs | 3 ++- src/wg.rs | 6 +++--- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a42eb07..cdc9d7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1025,9 +1025,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smoltcp" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" dependencies = [ "bitflags 1.3.2", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index a318177..29b947a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ rand = "0.8" nom = "7" async-trait = "0.1" priority-queue = "1.3" -smoltcp = { version = "0.11", default-features = false, features = [ +smoltcp = { version = "0.12", default-features = false, features = [ "std", "log", "medium-ip", diff --git a/src/virtual_device.rs b/src/virtual_device.rs index febe2ac..28d8751 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -109,11 +109,11 @@ pub struct RxToken { } impl smoltcp::phy::RxToken for RxToken { - fn consume(mut self, f: F) -> R + fn consume(self, f: F) -> R where - F: FnOnce(&mut [u8]) -> R, + F: FnOnce(&[u8]) -> R, { - f(&mut self.buffer) + f(&self.buffer) } } diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs index 39d45ac..3a3fd8d 100644 --- a/src/virtual_iface/tcp.rs +++ b/src/virtual_iface/tcp.rs @@ -6,6 +6,7 @@ use crate::Bus; use anyhow::Context; use async_trait::async_trait; use bytes::Bytes; +use smoltcp::iface::PollResult; use smoltcp::{ iface::{Config, Interface, SocketHandle, SocketSet}, socket::tcp, @@ -141,7 +142,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface { } }); - if iface.poll(loop_start, &mut device, &mut self.sockets) { + if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged { log::trace!("TCP virtual interface polled some packets to be processed"); } diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs index c72c51c..cb643c9 100644 --- a/src/virtual_iface/udp.rs +++ b/src/virtual_iface/udp.rs @@ -6,6 +6,7 @@ use crate::{Bus, PortProtocol}; use anyhow::Context; use async_trait::async_trait; use bytes::Bytes; +use smoltcp::iface::PollResult; use smoltcp::{ iface::{Config, Interface, SocketHandle, SocketSet}, socket::udp::{self, UdpMetadata}, @@ -140,7 +141,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface { } => { let loop_start = smoltcp::time::Instant::now(); - if iface.poll(loop_start, &mut device, &mut self.sockets) { + if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged { log::trace!("UDP virtual interface polled some packets to be processed"); } diff --git a/src/wg.rs b/src/wg.rs index fc346a2..f114c43 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -1,4 +1,4 @@ -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::net::{IpAddr, SocketAddr}; use std::time::Duration; use crate::Bus; @@ -253,7 +253,7 @@ impl WireGuardTunnel { Ok(IpVersion::Ipv4) => Ipv4Packet::new_checked(&packet) .ok() // Only care if the packet is destined for this tunnel - .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip) + .filter(|packet| packet.dst_addr() == self.source_peer_ip) .and_then(|packet| match packet.next_header() { IpProtocol::Tcp => Some(PortProtocol::Tcp), IpProtocol::Udp => Some(PortProtocol::Udp), @@ -263,7 +263,7 @@ impl WireGuardTunnel { Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet) .ok() // Only care if the packet is destined for this tunnel - .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip) + .filter(|packet| packet.dst_addr() == self.source_peer_ip) .and_then(|packet| match packet.next_header() { IpProtocol::Tcp => Some(PortProtocol::Tcp), IpProtocol::Udp => Some(PortProtocol::Udp), From 991eef0311055f901639161e153612f383149067 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:27:12 -0500 Subject: [PATCH 149/165] chore: update MSRV to 1.80.0 --- .github/workflows/build.yml | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9968a9e..e216219 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.78.0 + - 1.80.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: matrix: rust: - stable - - 1.78.0 + - 1.80.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/README.md b/README.md index 28db5ef..53196ae 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For example, ## Download -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.78.0: +onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.80.0: ```shell cargo install onetun @@ -37,7 +37,7 @@ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ 0.0.0.0:8080:192.168.4.2:8080 [...options...] ``` -You can also build onetun locally, using Rust ≥1.78.0: +You can also build onetun locally, using Rust ≥1.80.0: ```shell git clone https://github.com/aramperes/onetun && cd onetun From 6b2f6148c6de7715103ae6c48719d07d5a11c67e Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:29:59 -0500 Subject: [PATCH 150/165] chore: add linux-aarch64 build --- .github/workflows/release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e70aa8..882898c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,12 +75,16 @@ jobs: RUST_BACKTRACE: 1 strategy: matrix: - build: [ linux-amd64, macos-aarch64, windows ] + build: [ linux-amd64, linux-aarch64, macos-aarch64, windows ] include: - build: linux-amd64 os: ubuntu-latest rust: stable target: x86_64-unknown-linux-musl + - build: linux-aarch64 + os: ubuntu-latest + rust: stable + target: aarch64-unknown-linux-musl - build: macos-aarch64 os: macos-latest rust: stable From d307a118194bcd04f88a6744915e6aff5048da58 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:33:13 -0500 Subject: [PATCH 151/165] release: v0.3.10 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdc9d7f..5de0dfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -706,7 +706,7 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "onetun" -version = "0.3.9" +version = "0.3.10" dependencies = [ "anyhow", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 29b947a..e8aa50f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onetun" -version = "0.3.9" +version = "0.3.10" edition = "2021" license = "MIT" description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." From eb9c0be4371c7a005213eed0c45992110ce893b1 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:39:16 -0500 Subject: [PATCH 152/165] force cargo build with target --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 882898c..f38de99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -130,7 +130,7 @@ jobs: echo "release upload url: $release_upload_url" - name: Build onetun binary - run: cargo build --release + run: cargo build --release --target ${{ matrix.target }} - name: Prepare onetun binary shell: bash From 52d1d589ac266c88860b8ed275f0c3a31270f6a8 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:45:25 -0500 Subject: [PATCH 153/165] use cross to cross-build --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f38de99..6eda7d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,8 +129,11 @@ jobs: echo "RELEASE_UPLOAD_URL=$release_upload_url" >> $GITHUB_ENV echo "release upload url: $release_upload_url" + - name: Install cross + run: cargo install cross + - name: Build onetun binary - run: cargo build --release --target ${{ matrix.target }} + run: cross build --release --target ${{ matrix.target }} - name: Prepare onetun binary shell: bash From f75909fd8f8e24a0fa7a761b27f8e97b0990cf4d Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:56:37 -0500 Subject: [PATCH 154/165] fix: expected output location in release --- .github/workflows/release.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6eda7d3..7bfb991 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,18 +81,26 @@ jobs: os: ubuntu-latest rust: stable target: x86_64-unknown-linux-musl + cross: true + output: target/x86_64-unknown-linux-musl/release/onetun - build: linux-aarch64 os: ubuntu-latest rust: stable target: aarch64-unknown-linux-musl + cross: true + output: target/aarch64-unknown-linux-musl/release/onetun - build: macos-aarch64 os: macos-latest rust: stable target: aarch64-apple-darwin + cross: false + output: target/release/onetun - build: windows os: windows-2019 rust: stable target: x86_64-pc-windows-msvc + cross: false + output: target/release/onetun.exe steps: - name: Checkout repository @@ -129,21 +137,25 @@ jobs: echo "RELEASE_UPLOAD_URL=$release_upload_url" >> $GITHUB_ENV echo "release upload url: $release_upload_url" - - name: Install cross - run: cargo install cross - - name: Build onetun binary - run: cross build --release --target ${{ matrix.target }} + shell: bash + run: | + if [ "${{ matrix.cross }}" = "true" ]; then + cargo install cross + cross build --release --target ${{ matrix.target }} + else + cargo build --release --target ${{ matrix.target }} + fi - name: Prepare onetun binary shell: bash run: | mkdir -p ci/assets if [ "${{ matrix.build }}" = "windows" ]; then - cp "target/release/onetun.exe" "ci/assets/onetun.exe" + cp "${{ matrix.output }}" "ci/assets/onetun.exe" echo "ASSET=onetun.exe" >> $GITHUB_ENV else - cp "target/release/onetun" "ci/assets/onetun-${{ matrix.build }}" + cp "${{ matrix.output }}" "ci/assets/onetun-${{ matrix.build }}" echo "ASSET=onetun-${{ matrix.build }}" >> $GITHUB_ENV fi From c6544cfe05e1ba6fb26cb25bf093aa29fdfb5653 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:01:27 -0500 Subject: [PATCH 155/165] fix: assume path is target --- .github/workflows/release.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7bfb991..75532c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,25 +82,21 @@ jobs: rust: stable target: x86_64-unknown-linux-musl cross: true - output: target/x86_64-unknown-linux-musl/release/onetun - build: linux-aarch64 os: ubuntu-latest rust: stable target: aarch64-unknown-linux-musl cross: true - output: target/aarch64-unknown-linux-musl/release/onetun - build: macos-aarch64 os: macos-latest rust: stable target: aarch64-apple-darwin cross: false - output: target/release/onetun - build: windows os: windows-2019 rust: stable target: x86_64-pc-windows-msvc cross: false - output: target/release/onetun.exe steps: - name: Checkout repository @@ -152,10 +148,10 @@ jobs: run: | mkdir -p ci/assets if [ "${{ matrix.build }}" = "windows" ]; then - cp "${{ matrix.output }}" "ci/assets/onetun.exe" + cp "target/${{ matrix.output }}/release/onetun.exe" "ci/assets/onetun.exe" echo "ASSET=onetun.exe" >> $GITHUB_ENV else - cp "${{ matrix.output }}" "ci/assets/onetun-${{ matrix.build }}" + cp "target/${{ matrix.output }}/release/onetun" "ci/assets/onetun-${{ matrix.build }}" echo "ASSET=onetun-${{ matrix.build }}" >> $GITHUB_ENV fi From 89c3b59610d4b0f376f481af9010479929f06410 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:03:32 -0500 Subject: [PATCH 156/165] fix: typo --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75532c9..39b9be6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,10 +148,10 @@ jobs: run: | mkdir -p ci/assets if [ "${{ matrix.build }}" = "windows" ]; then - cp "target/${{ matrix.output }}/release/onetun.exe" "ci/assets/onetun.exe" + cp "target/${{ matrix.target }}/release/onetun.exe" "ci/assets/onetun.exe" echo "ASSET=onetun.exe" >> $GITHUB_ENV else - cp "target/${{ matrix.output }}/release/onetun" "ci/assets/onetun-${{ matrix.build }}" + cp "target/${{ matrix.target }}/release/onetun" "ci/assets/onetun-${{ matrix.build }}" echo "ASSET=onetun-${{ matrix.build }}" >> $GITHUB_ENV fi From 83ef02c6952229abc6f0669958441bed928eada4 Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:37:33 -0500 Subject: [PATCH 157/165] Create dependabot.yml --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..382be60 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + rebase-strategy: "disabled" From 672223790279a7b919e313b185f29ee7cbca6623 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 03:38:32 +0000 Subject: [PATCH 158/165] Bump priority-queue from 1.4.0 to 2.1.1 Bumps [priority-queue](https://github.com/garro95/priority-queue) from 1.4.0 to 2.1.1. - [Release notes](https://github.com/garro95/priority-queue/releases) - [Commits](https://github.com/garro95/priority-queue/commits/2.1.1) --- updated-dependencies: - dependency-name: priority-queue dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Cargo.lock | 21 ++++++++++++++------- Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5de0dfb..ad6ebf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,6 +360,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.10" @@ -503,9 +509,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heapless" @@ -552,11 +558,11 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] @@ -805,11 +811,12 @@ dependencies = [ [[package]] name = "priority-queue" -version = "1.4.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bda9164fe05bc9225752d54aae413343c36f684380005398a6a8fde95fe785" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", + "equivalent", "indexmap", ] diff --git a/Cargo.toml b/Cargo.toml index e8aa50f..2c54004 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3" rand = "0.8" nom = "7" async-trait = "0.1" -priority-queue = "1.3" +priority-queue = "2.1" smoltcp = { version = "0.12", default-features = false, features = [ "std", "log", From 2661a2d29fb6f05ac1689aa9b3bd31a69dc1eb11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 03:38:35 +0000 Subject: [PATCH 159/165] Bump pretty_env_logger from 0.4.0 to 0.5.0 Bumps [pretty_env_logger](https://github.com/seanmonstar/pretty-env-logger) from 0.4.0 to 0.5.0. - [Commits](https://github.com/seanmonstar/pretty-env-logger/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: pretty_env_logger dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 52 ++++++++++++++++++++-------------------------------- Cargo.toml | 2 +- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5de0dfb..9f5d1fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,17 +70,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -349,12 +338,12 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.7.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", @@ -519,12 +508,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -543,12 +529,9 @@ dependencies = [ [[package]] name = "humantime" -version = "1.3.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" @@ -591,6 +574,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "js-sys" version = "0.3.74" @@ -795,9 +789,9 @@ dependencies = [ [[package]] name = "pretty_env_logger" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ "env_logger", "log", @@ -844,12 +838,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "1.0.37" diff --git a/Cargo.toml b/Cargo.toml index e8aa50f..a15050b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ tracing = { version = "0.1", default-features = false, features = ["log"] } # bin-only dependencies clap = { version = "4.4.11", default-features = false, features = ["suggestions", "std", "env", "help", "wrap_help"], optional = true } -pretty_env_logger = { version = "0.4", optional = true } +pretty_env_logger = { version = "0.5", optional = true } async-recursion = "1.0" [features] From 08d99b9d22314c1067e98767ab803fbc0c7ac728 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:28:37 +0000 Subject: [PATCH 160/165] Bump anyhow from 1.0.93 to 1.0.94 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.93 to 1.0.94. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.93...1.0.94) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5de0dfb..25937f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "async-recursion" From 57e6ddc74c338bb7c1e33fa78e59c4727be570e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:28:44 +0000 Subject: [PATCH 161/165] Bump tokio from 1.41.1 to 1.42.0 Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.41.1 to 1.42.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.41.1...tokio-1.42.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5de0dfb..5e4d000 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1124,9 +1124,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", From d0fcab38c35125ffce837a7aafa1ba0ebe159bce Mon Sep 17 00:00:00 2001 From: Aram Peres <6775216+aramperes@users.noreply.github.com> Date: Sat, 25 Jan 2025 21:45:38 -0500 Subject: [PATCH 162/165] docs: update README and LICENSE --- LICENSE | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index a4f6b7c..d57c948 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Aram Peres +Copyright (c) 2025 Aram Peres Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 53196ae..58204f6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ onetun is available to install from [crates.io](https://crates.io/crates/onetun) cargo install onetun ``` -You can also download the binary for Windows, macOS (Intel), and Linux (amd64) from +You can also download the binary for Windows, macOS (Apple Silicon), and Linux (amd64, arm64) from the [Releases](https://github.com/aramperes/onetun/releases) page. You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun): @@ -319,4 +319,4 @@ Please consider opening a GitHub issue if you are unsure if your contribution is ## License -MIT License. See `LICENSE` for details. Copyright © 2023 Aram Peres. +MIT License. See `LICENSE` for details. Copyright © 2025 Aram Peres. From bcfa43702a65b2b3dfb37d730593dcc7a34c22f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:47:00 +0000 Subject: [PATCH 163/165] build(deps): bump async-trait from 0.1.83 to 0.1.87 Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.83 to 0.1.87. - [Release notes](https://github.com/dtolnay/async-trait/releases) - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.87) --- updated-dependencies: - dependency-name: async-trait dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8db0efe..dc56e5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", From 7eddf3f17f645d7722c7a3ae4e6061b2a783784e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:47:17 +0000 Subject: [PATCH 164/165] build(deps): bump anyhow from 1.0.94 to 1.0.97 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.94 to 1.0.97. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.94...1.0.97) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8db0efe..9db7951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "async-recursion" From 8030ca1a2d5aedef042e94d601b66725a15e0e43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:58:22 +0000 Subject: [PATCH 165/165] build(deps): bump tokio from 1.42.0 to 1.44.0 Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.42.0 to 1.44.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.42.0...tokio-1.44.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8db0efe..4b7d415 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "linux-raw-sys" @@ -1119,9 +1119,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.42.0" +version = "1.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" dependencies = [ "backtrace", "bytes", @@ -1135,9 +1135,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote",