mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 05:58:31 -04:00
Compare commits
48 commits
Author | SHA1 | Date | |
---|---|---|---|
3ca5ae2181 | |||
ac83ddbd4d | |||
17f424140d | |||
![]() |
8030ca1a2d | ||
![]() |
7eddf3f17f | ||
![]() |
bcfa43702a | ||
d0fcab38c3 | |||
c83c9ec500 | |||
caadd415cd | |||
3a89f2877d | |||
341849762c | |||
![]() |
57e6ddc74c | ||
![]() |
08d99b9d22 | ||
![]() |
2661a2d29f | ||
![]() |
6722237902 | ||
83ef02c695 | |||
89c3b59610 | |||
c6544cfe05 | |||
f75909fd8f | |||
52d1d589ac | |||
eb9c0be437 | |||
d307a11819 | |||
c4c52babae | |||
6b2f6148c6 | |||
991eef0311 | |||
0e93a6435a | |||
ca3590a4c0 | |||
784ab97c8b | |||
f3661c0a2c | |||
4fa8304799 | |||
1f3d9f035f | |||
06049161ab | |||
e26cca089f | |||
88ce124544 | |||
9ccd2e19f6 | |||
c86784ed70 | |||
e25c88410e | |||
![]() |
2b6d21572e | ||
56c950d159 | |||
ce40f85efa | |||
3ccd000ea8 | |||
5fd28164b5 | |||
1d703facc0 | |||
e23cfc3e7e | |||
0931ed496a | |||
91e6c79832 | |||
72ab679142 | |||
10b88ccc60 |
19 changed files with 761 additions and 563 deletions
4
.cargo/config.toml
Normal file
4
.cargo/config.toml
Normal file
|
@ -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"
|
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
|
@ -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"
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
- 1.70.0
|
- 1.80.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.70.0
|
- 1.80.0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
|
@ -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@v1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
@ -75,20 +75,28 @@ jobs:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
build: [ linux-amd64, macos-intel, windows ]
|
build: [ linux-amd64, linux-aarch64, macos-aarch64, 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
|
||||||
- build: macos-intel
|
cross: true
|
||||||
|
- 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: x86_64-apple-darwin
|
target: aarch64-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
|
||||||
|
@ -113,7 +121,7 @@ jobs:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Get release download URL
|
- name: Get release download URL
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
@ -126,17 +134,24 @@ jobs:
|
||||||
echo "release upload url: $release_upload_url"
|
echo "release upload url: $release_upload_url"
|
||||||
|
|
||||||
- name: Build onetun binary
|
- name: Build onetun binary
|
||||||
run: cargo build --release
|
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
|
- 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/release/onetun.exe" "ci/assets/onetun.exe"
|
cp "target/${{ matrix.target }}/release/onetun.exe" "ci/assets/onetun.exe"
|
||||||
echo "ASSET=onetun.exe" >> $GITHUB_ENV
|
echo "ASSET=onetun.exe" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
cp "target/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
|
echo "ASSET=onetun-${{ matrix.build }}" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
988
Cargo.lock
generated
988
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "onetun"
|
name = "onetun"
|
||||||
version = "0.3.7"
|
version = "0.3.10"
|
||||||
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.4.0", default-features = false }
|
boringtun = { version = "0.6.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,8 +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 = "1.3"
|
priority-queue = "2.1"
|
||||||
smoltcp = { version = "0.10", default-features = false, features = [
|
smoltcp = { version = "0.12", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"log",
|
"log",
|
||||||
"medium-ip",
|
"medium-ip",
|
||||||
|
@ -37,7 +37,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.4", optional = true }
|
pretty_env_logger = { version = "0.5", optional = true }
|
||||||
async-recursion = "1.0"
|
async-recursion = "1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM rust:1.70.0 as cargo-build
|
FROM rust:1.82.0 as cargo-build
|
||||||
|
|
||||||
WORKDIR /usr/src/onetun
|
WORKDIR /usr/src/onetun
|
||||||
COPY Cargo.toml Cargo.toml
|
COPY Cargo.toml Cargo.toml
|
||||||
|
@ -15,8 +15,9 @@ 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 \
|
||||||
RUN apt-get install dumb-init -y
|
&& 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
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
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
|
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
|
||||||
|
|
16
README.md
16
README.md
|
@ -21,13 +21,13 @@ For example,
|
||||||
|
|
||||||
## Download
|
## 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.80.0:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo install 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.
|
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.70.0:
|
You can also build onetun locally, using Rust ≥1.80.0:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/aramperes/onetun && cd onetun
|
git clone https://github.com/aramperes/onetun && cd onetun
|
||||||
|
@ -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.
|
... 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
|
||||||
|
@ -311,4 +319,4 @@ Please consider opening a GitHub issue if you are unsure if your contribution is
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License. See `LICENSE` for details. Copyright © 2023 Aram Peres.
|
MIT License. See `LICENSE` for details. Copyright © 2025 Aram Peres.
|
||||||
|
|
109
src/config.rs
109
src/config.rs
|
@ -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::Context;
|
use anyhow::{bail, Context};
|
||||||
pub use boringtun::crypto::{X25519PublicKey, X25519SecretKey};
|
pub use boringtun::x25519::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1";
|
const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1";
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
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<X25519SecretKey>,
|
pub private_key: Arc<StaticSecret>,
|
||||||
pub endpoint_public_key: Arc<X25519PublicKey>,
|
pub endpoint_public_key: Arc<PublicKey>,
|
||||||
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
|
||||||
.with_context(|| "Failed to parse port forward config")?
|
.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"))
|
||||||
.with_context(|| "Invalid source peer IP")?;
|
.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
|
||||||
.with_context(|| "Failed to parse remote port forward config")?
|
.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 {
|
||||||
return Err(anyhow::anyhow!("Remote port forward config <src_host> must match --source-peer-ip ({}), or be omitted.", source_peer_ip));
|
bail!("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() {
|
||||||
return Err(anyhow::anyhow!("No port forward configurations given."));
|
bail!("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())
|
||||||
.with_context(|| "Failed to read private key file")
|
.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,20 +238,18 @@ impl Config {
|
||||||
matches
|
matches
|
||||||
.get_one::<String>("private-key")
|
.get_one::<String>("private-key")
|
||||||
.cloned()
|
.cloned()
|
||||||
.with_context(|| "Missing private key")
|
.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"))
|
||||||
.with_context(|| "Invalid endpoint address")?;
|
.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)).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
|
// 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() {
|
||||||
return Err(anyhow::anyhow!(
|
bail!("Endpoint and bind addresses must be the same IP version");
|
||||||
"Endpoint and bind addresses must be the same IP version"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
addr
|
addr
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,21 +263,19 @@ impl Config {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
port_forwards,
|
port_forwards,
|
||||||
remote_port_forwards,
|
remote_port_forwards,
|
||||||
private_key: Arc::new(
|
private_key: Arc::new(parse_private_key(&private_key).context("Invalid private key")?),
|
||||||
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"))
|
||||||
.with_context(|| "Invalid endpoint public key")?,
|
.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"))
|
||||||
.with_context(|| "Invalid keep-alive value")?,
|
.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"))
|
||||||
.with_context(|| "Invalid max-transmission-unit value")?,
|
.context("Invalid max-transmission-unit value")?,
|
||||||
log: matches
|
log: matches
|
||||||
.get_one::<String>("log")
|
.get_one::<String>("log")
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -291,38 +287,47 @@ 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.with_context(|| "Missing address")?
|
s.context("Missing address")?
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.to_socket_addrs()
|
.to_socket_addrs()
|
||||||
.with_context(|| "Invalid address")?
|
.context("Invalid address")?
|
||||||
.next()
|
.next()
|
||||||
.with_context(|| "Could not lookup address")
|
.context("Could not lookup address")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ip(s: Option<&String>) -> anyhow::Result<IpAddr> {
|
fn parse_ip(s: Option<&String>) -> anyhow::Result<IpAddr> {
|
||||||
s.with_context(|| "Missing IP")?
|
s.context("Missing IP address")?
|
||||||
.parse::<IpAddr>()
|
.parse::<IpAddr>()
|
||||||
.with_context(|| "Invalid IP address")
|
.context("Invalid IP address")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_private_key(s: &str) -> anyhow::Result<X25519SecretKey> {
|
fn parse_private_key(s: &str) -> anyhow::Result<StaticSecret> {
|
||||||
s.parse::<X25519SecretKey>()
|
let decoded = base64::decode(s).context("Failed to decode private key")?;
|
||||||
.map_err(|e| anyhow::anyhow!("{}", e))
|
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<X25519PublicKey> {
|
fn parse_public_key(s: Option<&String>) -> anyhow::Result<PublicKey> {
|
||||||
s.with_context(|| "Missing public key")?
|
let encoded = s.context("Missing public key")?;
|
||||||
.parse::<X25519PublicKey>()
|
let decoded = base64::decode(encoded).context("Failed to decode public key")?;
|
||||||
.map_err(|e| anyhow::anyhow!("{}", e))
|
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() {
|
||||||
.with_context(|| "Invalid public key")
|
Ok(PublicKey::from(bytes))
|
||||||
|
} 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 psk = base64::decode(s).with_context(|| "Invalid pre-shared key")?;
|
let decoded = base64::decode(s).context("Failed to decode preshared key")?;
|
||||||
Ok(Some(psk.try_into().map_err(|_| {
|
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() {
|
||||||
anyhow::anyhow!("Unsupported pre-shared key")
|
Ok(Some(bytes))
|
||||||
})?))
|
} else {
|
||||||
|
bail!("Invalid preshared key")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -343,9 +348,7 @@ 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.with_context(|| "Missing MTU")?
|
s.context("Missing MTU")?.parse().context("Invalid MTU")
|
||||||
.parse()
|
|
||||||
.with_context(|| "Invalid MTU")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -474,27 +477,21 @@ impl PortForwardConfig {
|
||||||
|
|
||||||
let source = (
|
let source = (
|
||||||
src_addr.0.unwrap_or(default_source),
|
src_addr.0.unwrap_or(default_source),
|
||||||
src_addr
|
src_addr.1.parse::<u16>().context("Invalid source port")?,
|
||||||
.1
|
|
||||||
.parse::<u16>()
|
|
||||||
.with_context(|| "Invalid source port")?,
|
|
||||||
)
|
)
|
||||||
.to_socket_addrs()
|
.to_socket_addrs()
|
||||||
.with_context(|| "Invalid source address")?
|
.context("Invalid source address")?
|
||||||
.next()
|
.next()
|
||||||
.with_context(|| "Could not resolve source address")?;
|
.context("Could not resolve source address")?;
|
||||||
|
|
||||||
let destination = (
|
let destination = (
|
||||||
dst_addr.0,
|
dst_addr.0,
|
||||||
dst_addr
|
dst_addr.1.parse::<u16>().context("Invalid source port")?,
|
||||||
.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)
|
||||||
.with_context(|| "Invalid destination address")?
|
.context("Invalid destination address")?
|
||||||
.next()
|
.next()
|
||||||
.with_context(|| "Could not resolve destination address")?;
|
.context("Could not resolve destination address")?;
|
||||||
|
|
||||||
// Parse protocols
|
// Parse protocols
|
||||||
let protocols = if let Some(protocols) = protocols {
|
let protocols = if let Some(protocols) = protocols {
|
||||||
|
@ -504,7 +501,7 @@ impl PortForwardConfig {
|
||||||
} else {
|
} else {
|
||||||
Ok(vec![PortProtocol::Tcp])
|
Ok(vec![PortProtocol::Tcp])
|
||||||
}
|
}
|
||||||
.with_context(|| "Failed to parse protocols")?;
|
.context("Failed to parse protocols")?;
|
||||||
|
|
||||||
// Returns an config for each protocol
|
// Returns an config for each protocol
|
||||||
Ok(protocols
|
Ok(protocols
|
||||||
|
|
|
@ -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
|
||||||
.with_context(|| "Failed to initialize WireGuard tunnel")?;
|
.context("Failed to initialize WireGuard tunnel")?;
|
||||||
let wg = Arc::new(wg);
|
let wg = Arc::new(wg);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -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().with_context(|| "Failed to read config")?;
|
let config = Config::from_args().context("Configuration has errors")?;
|
||||||
init_logger(&config)?;
|
init_logger(&config)?;
|
||||||
|
|
||||||
for warning in &config.warnings {
|
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();
|
let mut builder = pretty_env_logger::formatted_timed_builder();
|
||||||
builder.parse_filters(&config.log);
|
builder.parse_filters(&config.log);
|
||||||
builder
|
builder.try_init().context("Failed to initialize logger")
|
||||||
.try_init()
|
|
||||||
.with_context(|| "Failed to initialize logger")
|
|
||||||
}
|
}
|
||||||
|
|
20
src/pcap.rs
20
src/pcap.rs
|
@ -16,7 +16,7 @@ impl Pcap {
|
||||||
self.writer
|
self.writer
|
||||||
.flush()
|
.flush()
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Failed to flush pcap writer")
|
.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
|
||||||
.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<()> {
|
async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> {
|
||||||
self.writer
|
self.writer
|
||||||
.write_u32(value)
|
.write_u32(value)
|
||||||
.await
|
.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<()> {
|
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
|
||||||
.with_context(|| "Failed to write packet header to pcap writer")?;
|
.context("Failed to write packet header to pcap writer")?;
|
||||||
self.write(packet)
|
self.write(packet)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Failed to write packet to pcap writer")?;
|
.context("Failed to write packet to pcap writer")?;
|
||||||
self.writer
|
self.writer
|
||||||
.flush()
|
.flush()
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Failed to flush pcap writer")?;
|
.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
|
||||||
.with_context(|| "Failed to create pcap file")?;
|
.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
|
||||||
.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);
|
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
|
||||||
.with_context(|| "Failed to write inbound IP packet to pcap writer")?;
|
.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
|
||||||
.with_context(|| "Failed to write output IP packet to pcap writer")?;
|
.context("Failed to write output IP packet to pcap writer")?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
.with_context(|| "Failed to listen on TCP proxy server")?;
|
.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
|
||||||
.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
|
// 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()
|
||||||
.with_context(|| "TCP virtual port pool is exhausted")?;
|
.context("TCP virtual port pool is exhausted")?;
|
||||||
Ok(VirtualPort::new(port, PortProtocol::Tcp))
|
Ok(VirtualPort::new(port, PortProtocol::Tcp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
.with_context(|| "Failed to bind on UDP proxy address")?;
|
.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
|
||||||
.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
|
// 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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.with_context(|| "virtual port pool is exhausted")?;
|
.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);
|
||||||
|
|
|
@ -55,8 +55,14 @@ impl VirtualIpDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl smoltcp::phy::Device for VirtualIpDevice {
|
impl smoltcp::phy::Device for VirtualIpDevice {
|
||||||
type RxToken<'a> = RxToken where Self: 'a;
|
type RxToken<'a>
|
||||||
type TxToken<'a> = TxToken where Self: 'a;
|
= RxToken
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
type TxToken<'a>
|
||||||
|
= TxToken
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
|
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
|
||||||
let next = {
|
let next = {
|
||||||
|
@ -103,11 +109,11 @@ pub struct RxToken {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl smoltcp::phy::RxToken for RxToken {
|
impl smoltcp::phy::RxToken for RxToken {
|
||||||
fn consume<R, F>(mut self, f: F) -> R
|
fn consume<R, F>(self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut [u8]) -> R,
|
F: FnOnce(&[u8]) -> R,
|
||||||
{
|
{
|
||||||
f(&mut self.buffer)
|
f(&self.buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::Bus;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use smoltcp::iface::PollResult;
|
||||||
use smoltcp::{
|
use smoltcp::{
|
||||||
iface::{Config, Interface, SocketHandle, SocketSet},
|
iface::{Config, Interface, SocketHandle, SocketSet},
|
||||||
socket::tcp,
|
socket::tcp,
|
||||||
|
@ -21,14 +22,14 @@ use std::{
|
||||||
const MAX_PACKET: usize = 65536;
|
const MAX_PACKET: usize = 65536;
|
||||||
|
|
||||||
/// A virtual interface for proxying Layer 7 data to Layer 3 packets, and vice-versa.
|
/// 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,
|
source_peer_ip: IpAddr,
|
||||||
port_forwards: Vec<PortForwardConfig>,
|
port_forwards: Vec<PortForwardConfig>,
|
||||||
bus: Bus,
|
bus: Bus,
|
||||||
sockets: SocketSet<'a>,
|
sockets: SocketSet<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TcpVirtualInterface<'a> {
|
impl TcpVirtualInterface {
|
||||||
/// Initialize the parameters for a new virtual interface.
|
/// Initialize the parameters for a new virtual interface.
|
||||||
/// Use the `poll_loop()` future to start the virtual interface poll loop.
|
/// Use the `poll_loop()` future to start the virtual interface poll loop.
|
||||||
pub fn new(port_forwards: Vec<PortForwardConfig>, bus: Bus, source_peer_ip: IpAddr) -> Self {
|
pub fn new(port_forwards: Vec<PortForwardConfig>, bus: Bus, source_peer_ip: IpAddr) -> Self {
|
||||||
|
@ -56,7 +57,7 @@ impl<'a> TcpVirtualInterface<'a> {
|
||||||
IpAddress::from(port_forward.destination.ip()),
|
IpAddress::from(port_forward.destination.ip()),
|
||||||
port_forward.destination.port(),
|
port_forward.destination.port(),
|
||||||
))
|
))
|
||||||
.with_context(|| "Virtual server socket failed to listen")?;
|
.context("Virtual server socket failed to listen")?;
|
||||||
|
|
||||||
Ok(socket)
|
Ok(socket)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +85,7 @@ impl<'a> TcpVirtualInterface<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(mut self, mut 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();
|
||||||
|
@ -94,7 +95,9 @@ impl VirtualInterfacePoll for TcpVirtualInterface<'_> {
|
||||||
let mut iface = Interface::new(config, &mut device, Instant::now());
|
let mut iface = Interface::new(config, &mut device, Instant::now());
|
||||||
iface.update_ip_addrs(|ip_addrs| {
|
iface.update_ip_addrs(|ip_addrs| {
|
||||||
addresses.into_iter().for_each(|addr| {
|
addresses.into_iter().for_each(|addr| {
|
||||||
ip_addrs.push(addr).unwrap();
|
ip_addrs
|
||||||
|
.push(addr)
|
||||||
|
.expect("maximum number of IPs in TCP interface reached");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,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");
|
log::trace!("TCP virtual interface polled some packets to be processed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +221,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface<'_> {
|
||||||
),
|
),
|
||||||
(IpAddress::from(self.source_peer_ip), virtual_port.num()),
|
(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;
|
next_poll = None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::{Bus, PortProtocol};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use smoltcp::iface::PollResult;
|
||||||
use smoltcp::{
|
use smoltcp::{
|
||||||
iface::{Config, Interface, SocketHandle, SocketSet},
|
iface::{Config, Interface, SocketHandle, SocketSet},
|
||||||
socket::udp::{self, UdpMetadata},
|
socket::udp::{self, UdpMetadata},
|
||||||
|
@ -20,14 +21,14 @@ use std::{
|
||||||
|
|
||||||
const MAX_PACKET: usize = 65536;
|
const MAX_PACKET: usize = 65536;
|
||||||
|
|
||||||
pub struct UdpVirtualInterface<'a> {
|
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<'a>,
|
sockets: SocketSet<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> UdpVirtualInterface<'a> {
|
impl UdpVirtualInterface {
|
||||||
/// Initialize the parameters for a new virtual interface.
|
/// Initialize the parameters for a new virtual interface.
|
||||||
/// Use the `poll_loop()` future to start the virtual interface poll loop.
|
/// Use the `poll_loop()` future to start the virtual interface poll loop.
|
||||||
pub fn new(port_forwards: Vec<PortForwardConfig>, bus: Bus, source_peer_ip: IpAddr) -> Self {
|
pub fn new(port_forwards: Vec<PortForwardConfig>, bus: Bus, source_peer_ip: IpAddr) -> Self {
|
||||||
|
@ -61,7 +62,7 @@ impl<'a> UdpVirtualInterface<'a> {
|
||||||
IpAddress::from(port_forward.destination.ip()),
|
IpAddress::from(port_forward.destination.ip()),
|
||||||
port_forward.destination.port(),
|
port_forward.destination.port(),
|
||||||
))
|
))
|
||||||
.with_context(|| "UDP virtual server socket failed to bind")?;
|
.context("UDP virtual server socket failed to bind")?;
|
||||||
Ok(socket)
|
Ok(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ impl<'a> UdpVirtualInterface<'a> {
|
||||||
let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
|
let mut socket = udp::Socket::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()))
|
||||||
.with_context(|| "UDP virtual client failed to bind")?;
|
.context("UDP virtual client failed to bind")?;
|
||||||
Ok(socket)
|
Ok(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ impl<'a> UdpVirtualInterface<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> {
|
impl VirtualInterfacePoll for UdpVirtualInterface {
|
||||||
async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> {
|
async fn poll_loop(mut self, mut 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();
|
||||||
|
@ -106,7 +107,9 @@ impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> {
|
||||||
let mut iface = Interface::new(config, &mut device, Instant::now());
|
let mut iface = Interface::new(config, &mut device, Instant::now());
|
||||||
iface.update_ip_addrs(|ip_addrs| {
|
iface.update_ip_addrs(|ip_addrs| {
|
||||||
addresses.into_iter().for_each(|addr| {
|
addresses.into_iter().for_each(|addr| {
|
||||||
ip_addrs.push(addr).unwrap();
|
ip_addrs
|
||||||
|
.push(addr)
|
||||||
|
.expect("maximum number of IPs in UDP interface reached");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -138,7 +141,7 @@ impl<'a> VirtualInterfacePoll for UdpVirtualInterface<'a> {
|
||||||
} => {
|
} => {
|
||||||
let loop_start = smoltcp::time::Instant::now();
|
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");
|
log::trace!("UDP virtual interface polled some packets to be processed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
49
src/wg.rs
49
src/wg.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::Bus;
|
use crate::Bus;
|
||||||
|
@ -9,6 +9,7 @@ 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;
|
||||||
|
@ -23,7 +24,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: Box<Tunn>,
|
peer: Mutex<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).
|
||||||
|
@ -36,11 +37,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 = Self::create_tunnel(config)?;
|
let peer = Mutex::new(Box::new(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
|
||||||
.with_context(|| "Failed to create UDP socket for WireGuard connection")?;
|
.context("Failed to create UDP socket for WireGuard connection")?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
source_peer_ip,
|
source_peer_ip,
|
||||||
|
@ -55,12 +56,16 @@ 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];
|
||||||
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) => {
|
TunnResult::WriteToNetwork(packet) => {
|
||||||
self.udp
|
self.udp
|
||||||
.send_to(packet, self.endpoint)
|
.send_to(packet, self.endpoint)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Failed to send encrypted IP packet to WireGuard endpoint.")?;
|
.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()
|
||||||
|
@ -104,7 +109,7 @@ impl WireGuardTunnel {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut send_buf = [0u8; MAX_PACKET];
|
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;
|
self.handle_routine_tun_result(tun_result).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +136,11 @@ 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.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
|
self.handle_routine_tun_result(result).await
|
||||||
}
|
}
|
||||||
|
@ -172,7 +181,11 @@ impl WireGuardTunnel {
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = &recv_buf[..size];
|
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) => {
|
TunnResult::WriteToNetwork(packet) => {
|
||||||
match self.udp.send_to(packet, self.endpoint).await {
|
match self.udp.send_to(packet, self.endpoint).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
@ -181,9 +194,10 @@ 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 self.peer.decapsulate(None, &[], &mut send_buf) {
|
match 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(_) => {}
|
||||||
|
@ -217,17 +231,20 @@ impl WireGuardTunnel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_tunnel(config: &Config) -> anyhow::Result<Box<Tunn>> {
|
fn create_tunnel(config: &Config) -> anyhow::Result<Tunn> {
|
||||||
|
let private = config.private_key.as_ref().clone();
|
||||||
|
let public = *config.endpoint_public_key.as_ref();
|
||||||
|
|
||||||
Tunn::new(
|
Tunn::new(
|
||||||
config.private_key.clone(),
|
private,
|
||||||
config.endpoint_public_key.clone(),
|
public,
|
||||||
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))
|
||||||
.with_context(|| "Failed to initialize boringtun Tunn")
|
.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).
|
||||||
|
@ -236,7 +253,7 @@ 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| 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() {
|
.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),
|
||||||
|
@ -246,7 +263,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| 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() {
|
.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),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue