Compare commits

..

No commits in common. "master" and "v0.3.6" have entirely different histories.

19 changed files with 560 additions and 980 deletions

View file

@ -1,4 +0,0 @@
[env]
# Each interface needs 1 IP allocated to the WireGuard peer IP.
# "8" = 7 tunnels per protocol.
SMOLTCP_IFACE_MAX_ADDR_COUNT = "8"

View file

@ -1,10 +0,0 @@
# 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"

View file

@ -10,7 +10,7 @@ jobs:
matrix: matrix:
rust: rust:
- stable - stable
- 1.80.0 - 1.70.0
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -39,7 +39,7 @@ jobs:
matrix: matrix:
rust: rust:
- stable - stable
- 1.80.0 - 1.70.0
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2 uses: actions/checkout@v2

View file

@ -61,7 +61,7 @@ jobs:
run: echo "${{ env.VERSION }}" > artifacts/release-version run: echo "${{ env.VERSION }}" > artifacts/release-version
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v1
with: with:
name: artifacts name: artifacts
path: artifacts path: artifacts
@ -75,28 +75,20 @@ jobs:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
strategy: strategy:
matrix: matrix:
build: [ linux-amd64, linux-aarch64, macos-aarch64, windows ] build: [ linux-amd64, macos-intel, windows ]
include: include:
- build: linux-amd64 - build: linux-amd64
os: ubuntu-latest os: ubuntu-latest
rust: stable rust: stable
target: x86_64-unknown-linux-musl target: x86_64-unknown-linux-musl
cross: true - build: macos-intel
- build: linux-aarch64
os: ubuntu-latest
rust: stable
target: aarch64-unknown-linux-musl
cross: true
- build: macos-aarch64
os: macos-latest os: macos-latest
rust: stable rust: stable
target: aarch64-apple-darwin target: x86_64-apple-darwin
cross: false
- build: windows - build: windows
os: windows-2019 os: windows-2019
rust: stable rust: stable
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
cross: false
steps: steps:
- name: Checkout repository - name: Checkout repository
@ -121,7 +113,7 @@ jobs:
target: ${{ matrix.target }} target: ${{ matrix.target }}
- name: Get release download URL - name: Get release download URL
uses: actions/download-artifact@v4 uses: actions/download-artifact@v1
with: with:
name: artifacts name: artifacts
path: artifacts path: artifacts
@ -134,24 +126,17 @@ jobs:
echo "release upload url: $release_upload_url" echo "release upload url: $release_upload_url"
- name: Build onetun binary - name: Build onetun binary
shell: bash run: cargo build --release
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 - name: Prepare onetun binary
shell: bash shell: bash
run: | run: |
mkdir -p ci/assets mkdir -p ci/assets
if [ "${{ matrix.build }}" = "windows" ]; then if [ "${{ matrix.build }}" = "windows" ]; then
cp "target/${{ matrix.target }}/release/onetun.exe" "ci/assets/onetun.exe" cp "target/release/onetun.exe" "ci/assets/onetun.exe"
echo "ASSET=onetun.exe" >> $GITHUB_ENV echo "ASSET=onetun.exe" >> $GITHUB_ENV
else else
cp "target/${{ matrix.target }}/release/onetun" "ci/assets/onetun-${{ matrix.build }}" cp "target/release/onetun" "ci/assets/onetun-${{ matrix.build }}"
echo "ASSET=onetun-${{ matrix.build }}" >> $GITHUB_ENV echo "ASSET=onetun-${{ matrix.build }}" >> $GITHUB_ENV
fi fi

956
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "onetun" name = "onetun"
version = "0.3.10" version = "0.3.6"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations."
@ -11,7 +11,7 @@ repository = "https://github.com/aramperes/onetun"
[dependencies] [dependencies]
# Required dependencies (bin and lib) # Required dependencies (bin and lib)
boringtun = { version = "0.6.0", default-features = false } boringtun = { version = "0.4.0", default-features = false }
log = "0.4" log = "0.4"
anyhow = "1" anyhow = "1"
tokio = { version = "1", features = [ "rt", "sync", "io-util", "net", "time", "fs", "macros" ] } tokio = { version = "1", features = [ "rt", "sync", "io-util", "net", "time", "fs", "macros" ] }
@ -19,16 +19,8 @@ futures = "0.3"
rand = "0.8" rand = "0.8"
nom = "7" nom = "7"
async-trait = "0.1" async-trait = "0.1"
priority-queue = "2.1" priority-queue = "1.3.0"
smoltcp = { version = "0.12", default-features = false, features = [ smoltcp = { version = "0.8.2", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] }
"std",
"log",
"medium-ip",
"proto-ipv4",
"proto-ipv6",
"socket-udp",
"socket-tcp",
] }
bytes = "1" bytes = "1"
base64 = "0.13" base64 = "0.13"
@ -37,7 +29,7 @@ tracing = { version = "0.1", default-features = false, features = ["log"] }
# bin-only dependencies # bin-only dependencies
clap = { version = "4.4.11", default-features = false, features = ["suggestions", "std", "env", "help", "wrap_help"], optional = true } clap = { version = "4.4.11", default-features = false, features = ["suggestions", "std", "env", "help", "wrap_help"], optional = true }
pretty_env_logger = { version = "0.5", optional = true } pretty_env_logger = { version = "0.4", optional = true }
async-recursion = "1.0" async-recursion = "1.0"
[features] [features]

View file

@ -1,4 +1,4 @@
FROM rust:1.82.0 as cargo-build FROM rust:1.70.0 as cargo-build
WORKDIR /usr/src/onetun WORKDIR /usr/src/onetun
COPY Cargo.toml Cargo.toml COPY Cargo.toml Cargo.toml
@ -15,9 +15,8 @@ COPY . .
RUN cargo build --release RUN cargo build --release
FROM debian:11-slim FROM debian:11-slim
RUN apt-get update \ RUN apt-get update
&& apt-get install dumb-init -y \ RUN 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 COPY --from=cargo-build /usr/src/onetun/target/release/onetun /usr/local/bin/onetun

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2025 Aram Peres Copyright (c) 2023 Aram Peres
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -21,13 +21,13 @@ For example,
## Download ## Download
onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.80.0: onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.70.0:
```shell ```shell
cargo install onetun cargo install onetun
``` ```
You can also download the binary for Windows, macOS (Apple Silicon), and Linux (amd64, arm64) from You can also download the binary for Windows, macOS (Intel), and Linux (amd64) from
the [Releases](https://github.com/aramperes/onetun/releases) page. the [Releases](https://github.com/aramperes/onetun/releases) page.
You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun): You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/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...] 0.0.0.0:8080:192.168.4.2:8080 [...options...]
``` ```
You can also build onetun locally, using Rust ≥1.80.0: You can also build onetun locally, using Rust ≥1.70.0:
```shell ```shell
git clone https://github.com/aramperes/onetun && cd onetun git clone https://github.com/aramperes/onetun && cd onetun
@ -126,14 +126,6 @@ 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. ... 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 ### UDP Support
**onetun** supports UDP forwarding. You can add `:UDP` at the end of the port-forward configuration, or `UDP,TCP` to support **onetun** supports UDP forwarding. You can add `:UDP` at the end of the port-forward configuration, or `UDP,TCP` to support
@ -267,49 +259,6 @@ 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. 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 ## Contributing and Maintenance
I will gladly accept contributions to onetun, and set aside time to review all pull-requests. I will gladly accept contributions to onetun, and set aside time to review all pull-requests.
@ -319,4 +268,4 @@ Please consider opening a GitHub issue if you are unsure if your contribution is
## License ## License
MIT License. See `LICENSE` for details. Copyright © 2025 Aram Peres. MIT License. See `LICENSE` for details. Copyright © 2023 Aram Peres.

View file

@ -5,18 +5,18 @@ use std::fs::read_to_string;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::sync::Arc; use std::sync::Arc;
use anyhow::{bail, Context}; use anyhow::Context;
pub use boringtun::x25519::{PublicKey, StaticSecret}; pub use boringtun::crypto::{X25519PublicKey, X25519SecretKey};
const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1";
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Config { pub struct Config {
pub port_forwards: Vec<PortForwardConfig>, pub port_forwards: Vec<PortForwardConfig>,
#[allow(dead_code)] #[allow(dead_code)]
pub remote_port_forwards: Vec<PortForwardConfig>, pub remote_port_forwards: Vec<PortForwardConfig>,
pub private_key: Arc<StaticSecret>, pub private_key: Arc<X25519SecretKey>,
pub endpoint_public_key: Arc<PublicKey>, pub endpoint_public_key: Arc<X25519PublicKey>,
pub preshared_key: Option<[u8; 32]>, pub preshared_key: Option<[u8; 32]>,
pub endpoint_addr: SocketAddr, pub endpoint_addr: SocketAddr,
pub endpoint_bind_addr: SocketAddr, pub endpoint_bind_addr: SocketAddr,
@ -161,14 +161,14 @@ impl Config {
.map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE)) .map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE))
.collect(); .collect();
let port_forwards: Vec<PortForwardConfig> = port_forwards let port_forwards: Vec<PortForwardConfig> = port_forwards
.context("Failed to parse port forward config")? .with_context(|| "Failed to parse port forward config")?
.into_iter() .into_iter()
.flatten() .flatten()
.collect(); .collect();
// Read source-peer-ip // Read source-peer-ip
let source_peer_ip = parse_ip(matches.get_one::<String>("source-peer-ip")) let source_peer_ip = parse_ip(matches.get_one::<String>("source-peer-ip"))
.context("Invalid source peer IP")?; .with_context(|| "Invalid source peer IP")?;
// Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs // Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs
let mut port_forward_strings = HashSet::new(); let mut port_forward_strings = HashSet::new();
@ -196,20 +196,20 @@ impl Config {
}) })
.collect(); .collect();
let mut remote_port_forwards: Vec<PortForwardConfig> = remote_port_forwards let mut remote_port_forwards: Vec<PortForwardConfig> = remote_port_forwards
.context("Failed to parse remote port forward config")? .with_context(|| "Failed to parse remote port forward config")?
.into_iter() .into_iter()
.flatten() .flatten()
.collect(); .collect();
for port_forward in remote_port_forwards.iter_mut() { for port_forward in remote_port_forwards.iter_mut() {
if port_forward.source.ip() != source_peer_ip { if port_forward.source.ip() != source_peer_ip {
bail!("Remote port forward config <src_host> must match --source-peer-ip ({}), or be omitted.", source_peer_ip); return Err(anyhow::anyhow!("Remote port forward config <src_host> 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.source = SocketAddr::from((source_peer_ip, port_forward.source.port()));
port_forward.remote = true; port_forward.remote = true;
} }
if port_forwards.is_empty() && remote_port_forwards.is_empty() { if port_forwards.is_empty() && remote_port_forwards.is_empty() {
bail!("No port forward configurations given."); return Err(anyhow::anyhow!("No port forward configurations given."));
} }
// Read private key from file or CLI argument // Read private key from file or CLI argument
@ -229,7 +229,7 @@ impl Config {
{ {
read_to_string(private_key_file) read_to_string(private_key_file)
.map(|s| s.trim().to_string()) .map(|s| s.trim().to_string())
.context("Failed to read private key file") .with_context(|| "Failed to read private key file")
} else { } else {
if std::env::var("ONETUN_PRIVATE_KEY").is_err() { if std::env::var("ONETUN_PRIVATE_KEY").is_err() {
warnings.push("Private key was passed using CLI. This is insecure. \ warnings.push("Private key was passed using CLI. This is insecure. \
@ -238,18 +238,20 @@ impl Config {
matches matches
.get_one::<String>("private-key") .get_one::<String>("private-key")
.cloned() .cloned()
.context("Missing private key") .with_context(|| "Missing private key")
}?; }?;
let endpoint_addr = parse_addr(matches.get_one::<String>("endpoint-addr")) let endpoint_addr = parse_addr(matches.get_one::<String>("endpoint-addr"))
.context("Invalid endpoint address")?; .with_context(|| "Invalid endpoint address")?;
let endpoint_bind_addr = if let Some(addr) = matches.get_one::<String>("endpoint-bind-addr") let endpoint_bind_addr = if let Some(addr) = matches.get_one::<String>("endpoint-bind-addr")
{ {
let addr = parse_addr(Some(addr)).context("Invalid bind address")?; let addr = parse_addr(Some(addr)).with_context(|| "Invalid bind address")?;
// Make sure the bind address and endpoint address are the same IP version // Make sure the bind address and endpoint address are the same IP version
if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() { if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() {
bail!("Endpoint and bind addresses must be the same IP version"); return Err(anyhow::anyhow!(
"Endpoint and bind addresses must be the same IP version"
));
} }
addr addr
} else { } else {
@ -263,19 +265,21 @@ impl Config {
Ok(Self { Ok(Self {
port_forwards, port_forwards,
remote_port_forwards, remote_port_forwards,
private_key: Arc::new(parse_private_key(&private_key).context("Invalid private key")?), private_key: Arc::new(
parse_private_key(&private_key).with_context(|| "Invalid private key")?,
),
endpoint_public_key: Arc::new( endpoint_public_key: Arc::new(
parse_public_key(matches.get_one::<String>("endpoint-public-key")) parse_public_key(matches.get_one::<String>("endpoint-public-key"))
.context("Invalid endpoint public key")?, .with_context(|| "Invalid endpoint public key")?,
), ),
preshared_key: parse_preshared_key(matches.get_one::<String>("preshared-key"))?, preshared_key: parse_preshared_key(matches.get_one::<String>("preshared-key"))?,
endpoint_addr, endpoint_addr,
endpoint_bind_addr, endpoint_bind_addr,
source_peer_ip, source_peer_ip,
keepalive_seconds: parse_keep_alive(matches.get_one::<String>("keep-alive")) keepalive_seconds: parse_keep_alive(matches.get_one::<String>("keep-alive"))
.context("Invalid keep-alive value")?, .with_context(|| "Invalid keep-alive value")?,
max_transmission_unit: parse_mtu(matches.get_one::<String>("max-transmission-unit")) max_transmission_unit: parse_mtu(matches.get_one::<String>("max-transmission-unit"))
.context("Invalid max-transmission-unit value")?, .with_context(|| "Invalid max-transmission-unit value")?,
log: matches log: matches
.get_one::<String>("log") .get_one::<String>("log")
.cloned() .cloned()
@ -287,47 +291,38 @@ impl Config {
} }
fn parse_addr<T: AsRef<str>>(s: Option<T>) -> anyhow::Result<SocketAddr> { fn parse_addr<T: AsRef<str>>(s: Option<T>) -> anyhow::Result<SocketAddr> {
s.context("Missing address")? s.with_context(|| "Missing address")?
.as_ref() .as_ref()
.to_socket_addrs() .to_socket_addrs()
.context("Invalid address")? .with_context(|| "Invalid address")?
.next() .next()
.context("Could not lookup address") .with_context(|| "Could not lookup address")
} }
fn parse_ip(s: Option<&String>) -> anyhow::Result<IpAddr> { fn parse_ip(s: Option<&String>) -> anyhow::Result<IpAddr> {
s.context("Missing IP address")? s.with_context(|| "Missing IP")?
.parse::<IpAddr>() .parse::<IpAddr>()
.context("Invalid IP address") .with_context(|| "Invalid IP address")
} }
fn parse_private_key(s: &str) -> anyhow::Result<StaticSecret> { fn parse_private_key(s: &str) -> anyhow::Result<X25519SecretKey> {
let decoded = base64::decode(s).context("Failed to decode private key")?; s.parse::<X25519SecretKey>()
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { .map_err(|e| anyhow::anyhow!("{}", e))
Ok(StaticSecret::from(bytes))
} else {
bail!("Invalid private key")
}
} }
fn parse_public_key(s: Option<&String>) -> anyhow::Result<PublicKey> { fn parse_public_key(s: Option<&String>) -> anyhow::Result<X25519PublicKey> {
let encoded = s.context("Missing public key")?; s.with_context(|| "Missing public key")?
let decoded = base64::decode(encoded).context("Failed to decode public key")?; .parse::<X25519PublicKey>()
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { .map_err(|e| anyhow::anyhow!("{}", e))
Ok(PublicKey::from(bytes)) .with_context(|| "Invalid public key")
} else {
bail!("Invalid public key")
}
} }
fn parse_preshared_key(s: Option<&String>) -> anyhow::Result<Option<[u8; 32]>> { fn parse_preshared_key(s: Option<&String>) -> anyhow::Result<Option<[u8; 32]>> {
if let Some(s) = s { if let Some(s) = s {
let decoded = base64::decode(s).context("Failed to decode preshared key")?; let psk = base64::decode(s).with_context(|| "Invalid pre-shared key")?;
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { Ok(Some(psk.try_into().map_err(|_| {
Ok(Some(bytes)) anyhow::anyhow!("Unsupported pre-shared key")
} else { })?))
bail!("Invalid preshared key")
}
} else { } else {
Ok(None) Ok(None)
} }
@ -348,7 +343,9 @@ fn parse_keep_alive(s: Option<&String>) -> anyhow::Result<Option<u16>> {
} }
fn parse_mtu(s: Option<&String>) -> anyhow::Result<usize> { fn parse_mtu(s: Option<&String>) -> anyhow::Result<usize> {
s.context("Missing MTU")?.parse().context("Invalid MTU") s.with_context(|| "Missing MTU")?
.parse()
.with_context(|| "Invalid MTU")
} }
#[cfg(unix)] #[cfg(unix)]
@ -477,21 +474,27 @@ impl PortForwardConfig {
let source = ( let source = (
src_addr.0.unwrap_or(default_source), src_addr.0.unwrap_or(default_source),
src_addr.1.parse::<u16>().context("Invalid source port")?, src_addr
.1
.parse::<u16>()
.with_context(|| "Invalid source port")?,
) )
.to_socket_addrs() .to_socket_addrs()
.context("Invalid source address")? .with_context(|| "Invalid source address")?
.next() .next()
.context("Could not resolve source address")?; .with_context(|| "Could not resolve source address")?;
let destination = ( let destination = (
dst_addr.0, dst_addr.0,
dst_addr.1.parse::<u16>().context("Invalid source port")?, dst_addr
.1
.parse::<u16>()
.with_context(|| "Invalid source port")?,
) )
.to_socket_addrs() // TODO: Pass this as given and use DNS config instead (issue #15) .to_socket_addrs() // TODO: Pass this as given and use DNS config instead (issue #15)
.context("Invalid destination address")? .with_context(|| "Invalid destination address")?
.next() .next()
.context("Could not resolve destination address")?; .with_context(|| "Could not resolve destination address")?;
// Parse protocols // Parse protocols
let protocols = if let Some(protocols) = protocols { let protocols = if let Some(protocols) = protocols {
@ -501,7 +504,7 @@ impl PortForwardConfig {
} else { } else {
Ok(vec![PortProtocol::Tcp]) Ok(vec![PortProtocol::Tcp])
} }
.context("Failed to parse protocols")?; .with_context(|| "Failed to parse protocols")?;
// Returns an config for each protocol // Returns an config for each protocol
Ok(protocols Ok(protocols

View file

@ -41,7 +41,7 @@ pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> {
let wg = WireGuardTunnel::new(&config, bus.clone()) let wg = WireGuardTunnel::new(&config, bus.clone())
.await .await
.context("Failed to initialize WireGuard tunnel")?; .with_context(|| "Failed to initialize WireGuard tunnel")?;
let wg = Arc::new(wg); let wg = Arc::new(wg);
{ {

View file

@ -8,7 +8,7 @@ async fn main() -> anyhow::Result<()> {
use anyhow::Context; use anyhow::Context;
use onetun::{config::Config, events::Bus}; use onetun::{config::Config, events::Bus};
let config = Config::from_args().context("Configuration has errors")?; let config = Config::from_args().with_context(|| "Failed to read config")?;
init_logger(&config)?; init_logger(&config)?;
for warning in &config.warnings { for warning in &config.warnings {
@ -32,5 +32,7 @@ fn init_logger(config: &onetun::config::Config) -> anyhow::Result<()> {
let mut builder = pretty_env_logger::formatted_timed_builder(); let mut builder = pretty_env_logger::formatted_timed_builder();
builder.parse_filters(&config.log); builder.parse_filters(&config.log);
builder.try_init().context("Failed to initialize logger") builder
.try_init()
.with_context(|| "Failed to initialize logger")
} }

View file

@ -16,7 +16,7 @@ impl Pcap {
self.writer self.writer
.flush() .flush()
.await .await
.context("Failed to flush pcap writer") .with_context(|| "Failed to flush pcap writer")
} }
async fn write(&mut self, data: &[u8]) -> anyhow::Result<usize> { async fn write(&mut self, data: &[u8]) -> anyhow::Result<usize> {
@ -30,14 +30,14 @@ impl Pcap {
self.writer self.writer
.write_u16(value) .write_u16(value)
.await .await
.context("Failed to write u16 to pcap writer") .with_context(|| "Failed to write u16 to pcap writer")
} }
async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> { async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> {
self.writer self.writer
.write_u32(value) .write_u32(value)
.await .await
.context("Failed to write u32 to pcap writer") .with_context(|| "Failed to write u32 to pcap writer")
} }
async fn global_header(&mut self) -> anyhow::Result<()> { 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<()> { async fn packet(&mut self, timestamp: Instant, packet: &[u8]) -> anyhow::Result<()> {
self.packet_header(timestamp, packet.len()) self.packet_header(timestamp, packet.len())
.await .await
.context("Failed to write packet header to pcap writer")?; .with_context(|| "Failed to write packet header to pcap writer")?;
self.write(packet) self.write(packet)
.await .await
.context("Failed to write packet to pcap writer")?; .with_context(|| "Failed to write packet to pcap writer")?;
self.writer self.writer
.flush() .flush()
.await .await
.context("Failed to flush pcap writer")?; .with_context(|| "Failed to flush pcap writer")?;
self.flush().await 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 mut endpoint = bus.new_endpoint();
let file = File::create(&pcap_file) let file = File::create(&pcap_file)
.await .await
.context("Failed to create pcap file")?; .with_context(|| "Failed to create pcap file")?;
let writer = BufWriter::new(file); let writer = BufWriter::new(file);
let mut writer = Pcap { writer }; let mut writer = Pcap { writer };
writer writer
.global_header() .global_header()
.await .await
.context("Failed to write global header to pcap writer")?; .with_context(|| "Failed to write global header to pcap writer")?;
info!("Capturing WireGuard IP packets to {}", &pcap_file); info!("Capturing WireGuard IP packets to {}", &pcap_file);
loop { loop {
@ -98,14 +98,14 @@ pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> {
writer writer
.packet(instant, &ip) .packet(instant, &ip)
.await .await
.context("Failed to write inbound IP packet to pcap writer")?; .with_context(|| "Failed to write inbound IP packet to pcap writer")?;
} }
Event::OutboundInternetPacket(ip) => { Event::OutboundInternetPacket(ip) => {
let instant = Instant::now(); let instant = Instant::now();
writer writer
.packet(instant, &ip) .packet(instant, &ip)
.await .await
.context("Failed to write output IP packet to pcap writer")?; .with_context(|| "Failed to write output IP packet to pcap writer")?;
} }
_ => {} _ => {}
} }

View file

@ -27,14 +27,14 @@ pub async fn tcp_proxy_server(
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let listener = TcpListener::bind(port_forward.source) let listener = TcpListener::bind(port_forward.source)
.await .await
.context("Failed to listen on TCP proxy server")?; .with_context(|| "Failed to listen on TCP proxy server")?;
loop { loop {
let port_pool = port_pool.clone(); let port_pool = port_pool.clone();
let (socket, peer_addr) = listener let (socket, peer_addr) = listener
.accept() .accept()
.await .await
.context("Failed to accept connection on TCP proxy server")?; .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 // 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 // received from the WireGuard tunnel. It is the port number that the virtual client will
@ -192,7 +192,7 @@ impl TcpPortPool {
let port = inner let port = inner
.queue .queue
.pop_front() .pop_front()
.context("TCP virtual port pool is exhausted")?; .with_context(|| "TCP virtual port pool is exhausted")?;
Ok(VirtualPort::new(port, PortProtocol::Tcp)) Ok(VirtualPort::new(port, PortProtocol::Tcp))
} }

View file

@ -37,7 +37,7 @@ pub async fn udp_proxy_server(
let mut endpoint = bus.new_endpoint(); let mut endpoint = bus.new_endpoint();
let socket = UdpSocket::bind(port_forward.source) let socket = UdpSocket::bind(port_forward.source)
.await .await
.context("Failed to bind on UDP proxy address")?; .with_context(|| "Failed to bind on UDP proxy address")?;
let mut buffer = [0u8; MAX_PACKET]; let mut buffer = [0u8; MAX_PACKET];
loop { loop {
@ -103,7 +103,7 @@ async fn next_udp_datagram(
let (size, peer_addr) = socket let (size, peer_addr) = socket
.recv_from(buffer) .recv_from(buffer)
.await .await
.context("Failed to accept incoming UDP datagram")?; .with_context(|| "Failed to accept incoming UDP datagram")?;
// Assign a 'virtual port': this is a unique port number used to route IP packets // 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 // received from the WireGuard tunnel. It is the port number that the virtual client will
@ -212,7 +212,7 @@ impl UdpPortPool {
None None
} }
}) })
.context("Virtual port pool is exhausted")?; .with_context(|| "virtual port pool is exhausted")?;
inner.port_by_peer_addr.insert(peer_addr, port); inner.port_by_peer_addr.insert(peer_addr, port);
inner.peer_addr_by_port.insert(port, peer_addr); inner.peer_addr_by_port.insert(port, peer_addr);

View file

@ -1,15 +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::config::PortProtocol;
use crate::events::{BusSender, Event}; use crate::events::{BusSender, Event};
use crate::Bus; 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. /// A virtual device that processes IP packets through smoltcp and WireGuard.
pub struct VirtualIpDevice { pub struct VirtualIpDevice {
@ -54,17 +52,11 @@ impl VirtualIpDevice {
} }
} }
impl smoltcp::phy::Device for VirtualIpDevice { impl<'a> Device<'a> for VirtualIpDevice {
type RxToken<'a> type RxToken = RxToken;
= RxToken type TxToken = TxToken;
where
Self: 'a;
type TxToken<'a>
= TxToken
where
Self: 'a;
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
let next = { let next = {
let mut queue = self let mut queue = self
.process_queue .process_queue
@ -89,7 +81,7 @@ impl smoltcp::phy::Device for VirtualIpDevice {
} }
} }
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> { fn transmit(&'a mut self) -> Option<Self::TxToken> {
Some(TxToken { Some(TxToken {
sender: self.bus_sender.clone(), sender: self.bus_sender.clone(),
}) })
@ -109,11 +101,11 @@ pub struct RxToken {
} }
impl smoltcp::phy::RxToken for RxToken { impl smoltcp::phy::RxToken for RxToken {
fn consume<R, F>(self, f: F) -> R fn consume<R, F>(mut self, _timestamp: Instant, f: F) -> smoltcp::Result<R>
where where
F: FnOnce(&[u8]) -> R, F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
{ {
f(&self.buffer) f(&mut self.buffer)
} }
} }
@ -123,9 +115,9 @@ pub struct TxToken {
} }
impl smoltcp::phy::TxToken for TxToken { impl smoltcp::phy::TxToken for TxToken {
fn consume<R, F>(self, len: usize, f: F) -> R fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result<R>
where where
F: FnOnce(&mut [u8]) -> R, F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
{ {
let mut buffer = vec![0; len]; let mut buffer = vec![0; len];
let result = f(&mut buffer); let result = f(&mut buffer);

View file

@ -1,23 +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::config::{PortForwardConfig, PortProtocol};
use crate::events::Event; use crate::events::Event;
use crate::virtual_device::VirtualIpDevice; use crate::virtual_device::VirtualIpDevice;
use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort};
use crate::Bus; 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,
time::Instant,
wire::{HardwareAddress, IpAddress, IpCidr, IpVersion},
};
use std::{
collections::{HashMap, HashSet, VecDeque},
net::IpAddr,
time::Duration,
};
const MAX_PACKET: usize = 65536; const MAX_PACKET: usize = 65536;
@ -26,7 +22,6 @@ pub struct TcpVirtualInterface {
source_peer_ip: IpAddr, source_peer_ip: IpAddr,
port_forwards: Vec<PortForwardConfig>, port_forwards: Vec<PortForwardConfig>,
bus: Bus, bus: Bus,
sockets: SocketSet<'static>,
} }
impl TcpVirtualInterface { impl TcpVirtualInterface {
@ -40,34 +35,33 @@ impl TcpVirtualInterface {
.collect(), .collect(),
source_peer_ip, source_peer_ip,
bus, bus,
sockets: SocketSet::new([]),
} }
} }
fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<tcp::Socket<'static>> { fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<TcpSocket<'static>> {
static mut TCP_SERVER_RX_DATA: [u8; 0] = []; static mut TCP_SERVER_RX_DATA: [u8; 0] = [];
static mut TCP_SERVER_TX_DATA: [u8; 0] = []; static mut TCP_SERVER_TX_DATA: [u8; 0] = [];
let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] });
let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] });
let mut socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
socket socket
.listen(( .listen((
IpAddress::from(port_forward.destination.ip()), IpAddress::from(port_forward.destination.ip()),
port_forward.destination.port(), port_forward.destination.port(),
)) ))
.context("Virtual server socket failed to listen")?; .with_context(|| "Virtual server socket failed to listen")?;
Ok(socket) Ok(socket)
} }
fn new_client_socket() -> anyhow::Result<tcp::Socket<'static>> { fn new_client_socket() -> anyhow::Result<TcpSocket<'static>> {
let rx_data = vec![0u8; MAX_PACKET]; let rx_data = vec![0u8; MAX_PACKET];
let tx_data = vec![0u8; MAX_PACKET]; let tx_data = vec![0u8; MAX_PACKET];
let tcp_rx_buffer = tcp::SocketBuffer::new(rx_data); let tcp_rx_buffer = TcpSocketBuffer::new(rx_data);
let tcp_tx_buffer = tcp::SocketBuffer::new(tx_data); let tcp_tx_buffer = TcpSocketBuffer::new(tx_data);
let socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
Ok(socket) Ok(socket)
} }
@ -79,32 +73,26 @@ impl TcpVirtualInterface {
} }
addresses addresses
.into_iter() .into_iter()
.map(|addr| IpCidr::new(addr, addr_length(&addr))) .map(|addr| IpCidr::new(addr, 32))
.collect() .collect()
} }
} }
#[async_trait] #[async_trait]
impl VirtualInterfacePoll for TcpVirtualInterface { impl VirtualInterfacePoll for TcpVirtualInterface {
async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { async fn poll_loop(self, device: VirtualIpDevice) -> anyhow::Result<()> {
// Create CIDR block for source peer IP + each port forward IP // Create CIDR block for source peer IP + each port forward IP
let addresses = self.addresses(); let addresses = self.addresses();
let config = Config::new(HardwareAddress::Ip);
// Create virtual interface (contains smoltcp state machine) // Create virtual interface (contains smoltcp state machine)
let mut iface = Interface::new(config, &mut device, Instant::now()); let mut iface = InterfaceBuilder::new(device, vec![])
iface.update_ip_addrs(|ip_addrs| { .ip_addrs(addresses)
addresses.into_iter().for_each(|addr| { .finalize();
ip_addrs
.push(addr)
.expect("maximum number of IPs in TCP interface reached");
});
});
// Create virtual server for each port forward // Create virtual server for each port forward
for port_forward in self.port_forwards.iter() { for port_forward in self.port_forwards.iter() {
let server_socket = TcpVirtualInterface::new_server_socket(*port_forward)?; let server_socket = TcpVirtualInterface::new_server_socket(*port_forward)?;
self.sockets.add(server_socket); iface.add_socket(server_socket);
} }
// The next time to poll the interface. Can be None for instant poll. // The next time to poll the interface. Can be None for instant poll.
@ -130,11 +118,11 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
// Find closed sockets // Find closed sockets
port_client_handle_map.retain(|virtual_port, client_handle| { port_client_handle_map.retain(|virtual_port, client_handle| {
let client_socket = self.sockets.get_mut::<tcp::Socket>(*client_handle); let client_socket = iface.get_socket::<TcpSocket>(*client_handle);
if client_socket.state() == tcp::State::Closed { if client_socket.state() == TcpState::Closed {
endpoint.send(Event::ClientConnectionDropped(*virtual_port)); endpoint.send(Event::ClientConnectionDropped(*virtual_port));
send_queue.remove(virtual_port); send_queue.remove(virtual_port);
self.sockets.remove(*client_handle); iface.remove_socket(*client_handle);
false false
} else { } else {
// Not closed, retain // Not closed, retain
@ -142,12 +130,16 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
} }
}); });
if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged { match iface.poll(loop_start) {
log::trace!("TCP virtual interface polled some packets to be processed"); Ok(processed) if processed => {
trace!("TCP virtual interface polled some packets to be processed");
}
Err(e) => error!("TCP virtual interface poll error: {:?}", e),
_ => {}
} }
for (virtual_port, client_handle) in port_client_handle_map.iter() { for (virtual_port, client_handle) in port_client_handle_map.iter() {
let client_socket = self.sockets.get_mut::<tcp::Socket>(*client_handle); let client_socket = iface.get_socket::<TcpSocket>(*client_handle);
if client_socket.can_send() { if client_socket.can_send() {
if let Some(send_queue) = send_queue.get_mut(virtual_port) { if let Some(send_queue) = send_queue.get_mut(virtual_port) {
let to_transfer = send_queue.pop_front(); let to_transfer = send_queue.pop_front();
@ -167,7 +159,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
); );
} }
} }
} else if client_socket.state() == tcp::State::CloseWait { } else if client_socket.state() == TcpState::CloseWait {
client_socket.close(); client_socket.close();
} }
} }
@ -190,7 +182,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
} }
// The virtual interface determines the next time to poll (this is to reduce unnecessary polls) // The virtual interface determines the next time to poll (this is to reduce unnecessary polls)
next_poll = match iface.poll_delay(loop_start, &self.sockets) { next_poll = match iface.poll_delay(loop_start) {
Some(smoltcp::time::Duration::ZERO) => None, Some(smoltcp::time::Duration::ZERO) => None,
Some(delay) => { Some(delay) => {
trace!("TCP Virtual interface delayed next poll by {}", delay); trace!("TCP Virtual interface delayed next poll by {}", delay);
@ -203,14 +195,13 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
match event { match event {
Event::ClientConnectionInitiated(port_forward, virtual_port) => { Event::ClientConnectionInitiated(port_forward, virtual_port) => {
let client_socket = TcpVirtualInterface::new_client_socket()?; let client_socket = TcpVirtualInterface::new_client_socket()?;
let client_handle = self.sockets.add(client_socket); let client_handle = iface.add_socket(client_socket);
// Add handle to map // Add handle to map
port_client_handle_map.insert(virtual_port, client_handle); port_client_handle_map.insert(virtual_port, client_handle);
send_queue.insert(virtual_port, VecDeque::new()); send_queue.insert(virtual_port, VecDeque::new());
let client_socket = self.sockets.get_mut::<tcp::Socket>(client_handle); let (client_socket, context) = iface.get_socket_and_context::<TcpSocket>(client_handle);
let context = iface.context();
client_socket client_socket
.connect( .connect(
@ -221,13 +212,13 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
), ),
(IpAddress::from(self.source_peer_ip), virtual_port.num()), (IpAddress::from(self.source_peer_ip), virtual_port.num()),
) )
.context("Virtual server socket failed to listen")?; .with_context(|| "Virtual server socket failed to listen")?;
next_poll = None; next_poll = None;
} }
Event::ClientConnectionDropped(virtual_port) => { Event::ClientConnectionDropped(virtual_port) => {
if let Some(client_handle) = port_client_handle_map.get(&virtual_port) { if let Some(client_handle) = port_client_handle_map.get(&virtual_port) {
let client_socket = self.sockets.get_mut::<tcp::Socket>(*client_handle); let client_socket = iface.get_socket::<TcpSocket>(*client_handle);
client_socket.close(); client_socket.close();
next_poll = None; next_poll = None;
} }
@ -248,10 +239,3 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
} }
} }
} }
const fn addr_length(addr: &IpAddress) -> u8 {
match addr.version() {
IpVersion::Ipv4 => 32,
IpVersion::Ipv6 => 128,
}
}

View file

@ -1,23 +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::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer};
use smoltcp::wire::{IpAddress, IpCidr};
use crate::config::PortForwardConfig; use crate::config::PortForwardConfig;
use crate::events::Event; use crate::events::Event;
use crate::virtual_device::VirtualIpDevice; use crate::virtual_device::VirtualIpDevice;
use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort};
use crate::{Bus, PortProtocol}; 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},
time::Instant,
wire::{HardwareAddress, IpAddress, IpCidr, IpVersion},
};
use std::{
collections::{HashMap, HashSet, VecDeque},
net::IpAddr,
time::Duration,
};
const MAX_PACKET: usize = 65536; const MAX_PACKET: usize = 65536;
@ -25,7 +21,6 @@ pub struct UdpVirtualInterface {
source_peer_ip: IpAddr, source_peer_ip: IpAddr,
port_forwards: Vec<PortForwardConfig>, port_forwards: Vec<PortForwardConfig>,
bus: Bus, bus: Bus,
sockets: SocketSet<'static>,
} }
impl UdpVirtualInterface { impl UdpVirtualInterface {
@ -39,47 +34,44 @@ impl UdpVirtualInterface {
.collect(), .collect(),
source_peer_ip, source_peer_ip,
bus, bus,
sockets: SocketSet::new([]),
} }
} }
fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<udp::Socket<'static>> { fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<UdpSocket<'static>> {
static mut UDP_SERVER_RX_META: [udp::PacketMetadata; 0] = []; static mut UDP_SERVER_RX_META: [UdpPacketMetadata; 0] = [];
static mut UDP_SERVER_RX_DATA: [u8; 0] = []; static mut UDP_SERVER_RX_DATA: [u8; 0] = [];
static mut UDP_SERVER_TX_META: [udp::PacketMetadata; 0] = []; static mut UDP_SERVER_TX_META: [UdpPacketMetadata; 0] = [];
static mut UDP_SERVER_TX_DATA: [u8; 0] = []; static mut UDP_SERVER_TX_DATA: [u8; 0] = [];
let udp_rx_buffer = let udp_rx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe {
udp::PacketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe { &mut UDP_SERVER_RX_DATA[..]
&mut UDP_SERVER_RX_DATA[..] });
}); let udp_tx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe {
let udp_tx_buffer = &mut UDP_SERVER_TX_DATA[..]
udp::PacketBuffer::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 mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
socket socket
.bind(( .bind((
IpAddress::from(port_forward.destination.ip()), IpAddress::from(port_forward.destination.ip()),
port_forward.destination.port(), port_forward.destination.port(),
)) ))
.context("UDP virtual server socket failed to bind")?; .with_context(|| "UDP virtual server socket failed to bind")?;
Ok(socket) Ok(socket)
} }
fn new_client_socket( fn new_client_socket(
source_peer_ip: IpAddr, source_peer_ip: IpAddr,
client_port: VirtualPort, client_port: VirtualPort,
) -> anyhow::Result<udp::Socket<'static>> { ) -> anyhow::Result<UdpSocket<'static>> {
let rx_meta = vec![udp::PacketMetadata::EMPTY; 10]; let rx_meta = vec![UdpPacketMetadata::EMPTY; 10];
let tx_meta = vec![udp::PacketMetadata::EMPTY; 10]; let tx_meta = vec![UdpPacketMetadata::EMPTY; 10];
let rx_data = vec![0u8; MAX_PACKET]; let rx_data = vec![0u8; MAX_PACKET];
let tx_data = vec![0u8; MAX_PACKET]; let tx_data = vec![0u8; MAX_PACKET];
let udp_rx_buffer = udp::PacketBuffer::new(rx_meta, rx_data); let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data);
let udp_tx_buffer = udp::PacketBuffer::new(tx_meta, tx_data); let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data);
let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer);
socket socket
.bind((IpAddress::from(source_peer_ip), client_port.num())) .bind((IpAddress::from(source_peer_ip), client_port.num()))
.context("UDP virtual client failed to bind")?; .with_context(|| "UDP virtual client failed to bind")?;
Ok(socket) Ok(socket)
} }
@ -91,32 +83,26 @@ impl UdpVirtualInterface {
} }
addresses addresses
.into_iter() .into_iter()
.map(|addr| IpCidr::new(addr, addr_length(&addr))) .map(|addr| IpCidr::new(addr, 32))
.collect() .collect()
} }
} }
#[async_trait] #[async_trait]
impl VirtualInterfacePoll for UdpVirtualInterface { impl VirtualInterfacePoll for UdpVirtualInterface {
async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { async fn poll_loop(self, device: VirtualIpDevice) -> anyhow::Result<()> {
// Create CIDR block for source peer IP + each port forward IP // Create CIDR block for source peer IP + each port forward IP
let addresses = self.addresses(); let addresses = self.addresses();
let config = Config::new(HardwareAddress::Ip);
// Create virtual interface (contains smoltcp state machine) // Create virtual interface (contains smoltcp state machine)
let mut iface = Interface::new(config, &mut device, Instant::now()); let mut iface = InterfaceBuilder::new(device, vec![])
iface.update_ip_addrs(|ip_addrs| { .ip_addrs(addresses)
addresses.into_iter().for_each(|addr| { .finalize();
ip_addrs
.push(addr)
.expect("maximum number of IPs in UDP interface reached");
});
});
// Create virtual server for each port forward // Create virtual server for each port forward
for port_forward in self.port_forwards.iter() { for port_forward in self.port_forwards.iter() {
let server_socket = UdpVirtualInterface::new_server_socket(*port_forward)?; let server_socket = UdpVirtualInterface::new_server_socket(*port_forward)?;
self.sockets.add(server_socket); iface.add_socket(server_socket);
} }
// The next time to poll the interface. Can be None for instant poll. // The next time to poll the interface. Can be None for instant poll.
@ -141,12 +127,16 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
} => { } => {
let loop_start = smoltcp::time::Instant::now(); let loop_start = smoltcp::time::Instant::now();
if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged { match iface.poll(loop_start) {
log::trace!("UDP virtual interface polled some packets to be processed"); Ok(processed) if processed => {
trace!("UDP virtual interface polled some packets to be processed");
}
Err(e) => error!("UDP virtual interface poll error: {:?}", e),
_ => {}
} }
for (virtual_port, client_handle) in port_client_handle_map.iter() { for (virtual_port, client_handle) in port_client_handle_map.iter() {
let client_socket = self.sockets.get_mut::<udp::Socket>(*client_handle); let client_socket = iface.get_socket::<UdpSocket>(*client_handle);
if client_socket.can_send() { if client_socket.can_send() {
if let Some(send_queue) = send_queue.get_mut(virtual_port) { if let Some(send_queue) = send_queue.get_mut(virtual_port) {
let to_transfer = send_queue.pop_front(); let to_transfer = send_queue.pop_front();
@ -154,7 +144,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
client_socket client_socket
.send_slice( .send_slice(
&data, &data,
UdpMetadata::from(port_forward.destination), (IpAddress::from(port_forward.destination.ip()), port_forward.destination.port()).into(),
) )
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
error!( error!(
@ -182,7 +172,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
} }
// The virtual interface determines the next time to poll (this is to reduce unnecessary polls) // The virtual interface determines the next time to poll (this is to reduce unnecessary polls)
next_poll = match iface.poll_delay(loop_start, &self.sockets) { next_poll = match iface.poll_delay(loop_start) {
Some(smoltcp::time::Duration::ZERO) => None, Some(smoltcp::time::Duration::ZERO) => None,
Some(delay) => { Some(delay) => {
trace!("UDP Virtual interface delayed next poll by {}", delay); trace!("UDP Virtual interface delayed next poll by {}", delay);
@ -200,7 +190,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
} else { } else {
// Client socket does not exist // Client socket does not exist
let client_socket = UdpVirtualInterface::new_client_socket(self.source_peer_ip, virtual_port)?; let client_socket = UdpVirtualInterface::new_client_socket(self.source_peer_ip, virtual_port)?;
let client_handle = self.sockets.add(client_socket); let client_handle = iface.add_socket(client_socket);
// Add handle to map // Add handle to map
port_client_handle_map.insert(virtual_port, client_handle); port_client_handle_map.insert(virtual_port, client_handle);
@ -218,10 +208,3 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
} }
} }
} }
const fn addr_length(addr: &IpAddress) -> u8 {
match addr.version() {
IpVersion::Ipv4 => 32,
IpVersion::Ipv6 => 128,
}
}

View file

@ -1,4 +1,4 @@
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::Duration; use std::time::Duration;
use crate::Bus; use crate::Bus;
@ -9,7 +9,6 @@ use boringtun::noise::{Tunn, TunnResult};
use log::Level; use log::Level;
use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet}; use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet};
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use tokio::sync::Mutex;
use crate::config::{Config, PortProtocol}; use crate::config::{Config, PortProtocol};
use crate::events::Event; use crate::events::Event;
@ -24,7 +23,7 @@ const MAX_PACKET: usize = 65536;
pub struct WireGuardTunnel { pub struct WireGuardTunnel {
pub(crate) source_peer_ip: IpAddr, pub(crate) source_peer_ip: IpAddr,
/// `boringtun` peer/tunnel implementation, used for crypto & WG protocol. /// `boringtun` peer/tunnel implementation, used for crypto & WG protocol.
peer: Mutex<Box<Tunn>>, peer: Box<Tunn>,
/// The UDP socket for the public WireGuard endpoint to connect to. /// The UDP socket for the public WireGuard endpoint to connect to.
udp: UdpSocket, udp: UdpSocket,
/// The address of the public WireGuard endpoint (UDP). /// The address of the public WireGuard endpoint (UDP).
@ -37,11 +36,11 @@ impl WireGuardTunnel {
/// Initialize a new WireGuard tunnel. /// Initialize a new WireGuard tunnel.
pub async fn new(config: &Config, bus: Bus) -> anyhow::Result<Self> { pub async fn new(config: &Config, bus: Bus) -> anyhow::Result<Self> {
let source_peer_ip = config.source_peer_ip; let source_peer_ip = config.source_peer_ip;
let peer = Mutex::new(Box::new(Self::create_tunnel(config)?)); let peer = Self::create_tunnel(config)?;
let endpoint = config.endpoint_addr; let endpoint = config.endpoint_addr;
let udp = UdpSocket::bind(config.endpoint_bind_addr) let udp = UdpSocket::bind(config.endpoint_bind_addr)
.await .await
.context("Failed to create UDP socket for WireGuard connection")?; .with_context(|| "Failed to create UDP socket for WireGuard connection")?;
Ok(Self { Ok(Self {
source_peer_ip, source_peer_ip,
@ -56,16 +55,12 @@ impl WireGuardTunnel {
pub async fn send_ip_packet(&self, packet: &[u8]) -> anyhow::Result<()> { pub async fn send_ip_packet(&self, packet: &[u8]) -> anyhow::Result<()> {
trace_ip_packet("Sending IP packet", packet); trace_ip_packet("Sending IP packet", packet);
let mut send_buf = [0u8; MAX_PACKET]; let mut send_buf = [0u8; MAX_PACKET];
let encapsulate_result = { match self.peer.encapsulate(packet, &mut send_buf) {
let mut peer = self.peer.lock().await;
peer.encapsulate(packet, &mut send_buf)
};
match encapsulate_result {
TunnResult::WriteToNetwork(packet) => { TunnResult::WriteToNetwork(packet) => {
self.udp self.udp
.send_to(packet, self.endpoint) .send_to(packet, self.endpoint)
.await .await
.context("Failed to send encrypted IP packet to WireGuard endpoint.")?; .with_context(|| "Failed to send encrypted IP packet to WireGuard endpoint.")?;
debug!( debug!(
"Sent {} bytes to WireGuard endpoint (encrypted IP packet)", "Sent {} bytes to WireGuard endpoint (encrypted IP packet)",
packet.len() packet.len()
@ -109,7 +104,7 @@ impl WireGuardTunnel {
loop { loop {
let mut send_buf = [0u8; MAX_PACKET]; let mut send_buf = [0u8; MAX_PACKET];
let tun_result = { self.peer.lock().await.update_timers(&mut send_buf) }; let tun_result = self.peer.update_timers(&mut send_buf);
self.handle_routine_tun_result(tun_result).await; self.handle_routine_tun_result(tun_result).await;
} }
} }
@ -136,11 +131,7 @@ impl WireGuardTunnel {
warn!("Wireguard handshake has expired!"); warn!("Wireguard handshake has expired!");
let mut buf = vec![0u8; MAX_PACKET]; let mut buf = vec![0u8; MAX_PACKET];
let result = self let result = self.peer.format_handshake_initiation(&mut buf[..], false);
.peer
.lock()
.await
.format_handshake_initiation(&mut buf[..], false);
self.handle_routine_tun_result(result).await self.handle_routine_tun_result(result).await
} }
@ -181,11 +172,7 @@ impl WireGuardTunnel {
}; };
let data = &recv_buf[..size]; let data = &recv_buf[..size];
let decapsulate_result = { match self.peer.decapsulate(None, data, &mut send_buf) {
let mut peer = self.peer.lock().await;
peer.decapsulate(None, data, &mut send_buf)
};
match decapsulate_result {
TunnResult::WriteToNetwork(packet) => { TunnResult::WriteToNetwork(packet) => {
match self.udp.send_to(packet, self.endpoint).await { match self.udp.send_to(packet, self.endpoint).await {
Ok(_) => {} Ok(_) => {}
@ -194,10 +181,9 @@ impl WireGuardTunnel {
continue; continue;
} }
}; };
let mut peer = self.peer.lock().await;
loop { loop {
let mut send_buf = [0u8; MAX_PACKET]; let mut send_buf = [0u8; MAX_PACKET];
match peer.decapsulate(None, &[], &mut send_buf) { match self.peer.decapsulate(None, &[], &mut send_buf) {
TunnResult::WriteToNetwork(packet) => { TunnResult::WriteToNetwork(packet) => {
match self.udp.send_to(packet, self.endpoint).await { match self.udp.send_to(packet, self.endpoint).await {
Ok(_) => {} Ok(_) => {}
@ -231,20 +217,17 @@ impl WireGuardTunnel {
} }
} }
fn create_tunnel(config: &Config) -> anyhow::Result<Tunn> { fn create_tunnel(config: &Config) -> anyhow::Result<Box<Tunn>> {
let private = config.private_key.as_ref().clone();
let public = *config.endpoint_public_key.as_ref();
Tunn::new( Tunn::new(
private, config.private_key.clone(),
public, config.endpoint_public_key.clone(),
config.preshared_key, config.preshared_key,
config.keepalive_seconds, config.keepalive_seconds,
0, 0,
None, None,
) )
.map_err(|s| anyhow::anyhow!("{}", s)) .map_err(|s| anyhow::anyhow!("{}", s))
.context("Failed to initialize boringtun Tunn") .with_context(|| "Failed to initialize boringtun Tunn")
} }
/// Determine the inner protocol of the incoming IP packet (TCP/UDP). /// Determine the inner protocol of the incoming IP packet (TCP/UDP).
@ -253,8 +236,8 @@ impl WireGuardTunnel {
Ok(IpVersion::Ipv4) => Ipv4Packet::new_checked(&packet) Ok(IpVersion::Ipv4) => Ipv4Packet::new_checked(&packet)
.ok() .ok()
// Only care if the packet is destined for this tunnel // Only care if the packet is destined for this tunnel
.filter(|packet| packet.dst_addr() == self.source_peer_ip) .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip)
.and_then(|packet| match packet.next_header() { .and_then(|packet| match packet.protocol() {
IpProtocol::Tcp => Some(PortProtocol::Tcp), IpProtocol::Tcp => Some(PortProtocol::Tcp),
IpProtocol::Udp => Some(PortProtocol::Udp), IpProtocol::Udp => Some(PortProtocol::Udp),
// Unrecognized protocol, so we cannot determine where to route // Unrecognized protocol, so we cannot determine where to route
@ -263,7 +246,7 @@ impl WireGuardTunnel {
Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet) Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet)
.ok() .ok()
// Only care if the packet is destined for this tunnel // Only care if the packet is destined for this tunnel
.filter(|packet| packet.dst_addr() == self.source_peer_ip) .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip)
.and_then(|packet| match packet.next_header() { .and_then(|packet| match packet.next_header() {
IpProtocol::Tcp => Some(PortProtocol::Tcp), IpProtocol::Tcp => Some(PortProtocol::Tcp),
IpProtocol::Udp => Some(PortProtocol::Udp), IpProtocol::Udp => Some(PortProtocol::Udp),