mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 05:58:31 -04:00
Compare commits
93 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 | |||
83beb48b07 | |||
21fe78f540 | |||
c3b752678e | |||
32f189e53a | |||
488a0e0807 | |||
![]() |
38fc217a29 | ||
ae15b4203c | |||
992e55bf91 | |||
a100f90a92 | |||
![]() |
1613d2bb5c | ||
![]() |
29fb98f02b | ||
767b83d241 | |||
731218d943 | |||
7200cc07e7 | |||
![]() |
1997ae7ea8 | ||
9f53198f17 | |||
998d1cfc8d | |||
0a06df59f9 | |||
61da97f4aa | |||
c5e803192f | |||
2896a4fcdb | |||
07e895c770 | |||
c0d0a5cb02 | |||
1333ea8a7c | |||
6f143280d1 | |||
![]() |
653c314409 | ||
43a20ef6b3 | |||
4f935c5a2d | |||
5dc04d9614 | |||
fa634a08dc | |||
76b6a6e346 | |||
e62b7d30fe | |||
0553fce5c6 | |||
6c64531940 | |||
77981371fc | |||
![]() |
fbc76e3fb0 | ||
85195d8aba | |||
eb9b8ff15d | |||
![]() |
074e1b430c | ||
![]() |
cea343c2c9 | ||
aef90a5c0c | |||
b78cab58ee | |||
8cee210ccb | |||
96be421495 | |||
c09a541788 |
22 changed files with 1358 additions and 648 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"
|
6
.github/ci/macos-install-packages
vendored
6
.github/ci/macos-install-packages
vendored
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
brew install asciidoctor
|
||||
# brew install asciidoctor
|
||||
|
||||
brew install openssl@1.1
|
||||
cp /usr/local/opt/openssl@1.1/lib/pkgconfig/*.pc /usr/local/lib/pkgconfig/
|
||||
# brew install openssl@1.1
|
||||
# cp /usr/local/opt/openssl@1.1/lib/pkgconfig/*.pc /usr/local/lib/pkgconfig/
|
||||
|
|
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:
|
||||
rust:
|
||||
- stable
|
||||
- 1.56.0
|
||||
- 1.80.0
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
@ -39,7 +39,7 @@ jobs:
|
|||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
- 1.56.0
|
||||
- 1.80.0
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
|
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
|
@ -61,7 +61,7 @@ jobs:
|
|||
run: echo "${{ env.VERSION }}" > artifacts/release-version
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts
|
||||
path: artifacts
|
||||
|
@ -75,20 +75,28 @@ jobs:
|
|||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
matrix:
|
||||
build: [ linux-amd64, macos-intel, windows ]
|
||||
build: [ linux-amd64, linux-aarch64, macos-aarch64, windows ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-18.04
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
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
|
||||
rust: stable
|
||||
target: x86_64-apple-darwin
|
||||
target: aarch64-apple-darwin
|
||||
cross: false
|
||||
- build: windows
|
||||
os: windows-2019
|
||||
rust: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
cross: false
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
@ -97,7 +105,7 @@ jobs:
|
|||
fetch-depth: 1
|
||||
|
||||
- name: Install packages (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
.github/ci/ubuntu-install-packages
|
||||
- name: Install packages (macOS)
|
||||
|
@ -113,7 +121,7 @@ jobs:
|
|||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Get release download URL
|
||||
uses: actions/download-artifact@v1
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifacts
|
||||
path: artifacts
|
||||
|
@ -126,17 +134,24 @@ jobs:
|
|||
echo "release upload url: $release_upload_url"
|
||||
|
||||
- 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
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ci/assets
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@
|
|||
.envrc
|
||||
*.log
|
||||
*.pcap
|
||||
.DS_Store
|
||||
|
|
1069
Cargo.lock
generated
1069
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
32
Cargo.toml
32
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "onetun"
|
||||
version = "0.3.2"
|
||||
version = "0.3.10"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations."
|
||||
|
@ -11,20 +11,34 @@ repository = "https://github.com/aramperes/onetun"
|
|||
|
||||
[dependencies]
|
||||
# Required dependencies (bin and lib)
|
||||
boringtun = { version = "0.4.0", default-features = false }
|
||||
boringtun = { version = "0.6.0", default-features = false }
|
||||
log = "0.4"
|
||||
anyhow = "1"
|
||||
tokio = { version = "1", features = [ "rt", "sync", "io-util", "net", "time", "fs", "macros" ] }
|
||||
futures = "0.3.17"
|
||||
rand = "0.8.4"
|
||||
futures = "0.3"
|
||||
rand = "0.8"
|
||||
nom = "7"
|
||||
async-trait = "0.1.51"
|
||||
priority-queue = "1.2.0"
|
||||
smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] }
|
||||
async-trait = "0.1"
|
||||
priority-queue = "2.1"
|
||||
smoltcp = { version = "0.12", default-features = false, features = [
|
||||
"std",
|
||||
"log",
|
||||
"medium-ip",
|
||||
"proto-ipv4",
|
||||
"proto-ipv6",
|
||||
"socket-udp",
|
||||
"socket-tcp",
|
||||
] }
|
||||
bytes = "1"
|
||||
base64 = "0.13"
|
||||
|
||||
# forward boringtuns tracing events to log
|
||||
tracing = { version = "0.1", default-features = false, features = ["log"] }
|
||||
|
||||
# bin-only dependencies
|
||||
clap = { version = "2.33", default-features = false, features = ["suggestions"], optional = true }
|
||||
pretty_env_logger = { version = "0.4", optional = true }
|
||||
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 }
|
||||
async-recursion = "1.0"
|
||||
|
||||
[features]
|
||||
pcap = []
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM rust:1.56 as cargo-build
|
||||
FROM rust:1.82.0 as cargo-build
|
||||
|
||||
WORKDIR /usr/src/onetun
|
||||
COPY Cargo.toml Cargo.toml
|
||||
|
@ -15,8 +15,9 @@ COPY . .
|
|||
RUN cargo build --release
|
||||
|
||||
FROM debian:11-slim
|
||||
RUN apt-get update
|
||||
RUN apt-get install dumb-init -y
|
||||
RUN apt-get update \
|
||||
&& apt-get install dumb-init -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=cargo-build /usr/src/onetun/target/release/onetun /usr/local/bin/onetun
|
||||
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021-2022 Aram Peres
|
||||
Copyright (c) 2025 Aram Peres
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
127
README.md
127
README.md
|
@ -21,28 +21,28 @@ For example,
|
|||
|
||||
## Download
|
||||
|
||||
onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.56:
|
||||
onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.80.0:
|
||||
|
||||
```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.
|
||||
|
||||
You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun):
|
||||
|
||||
```shell
|
||||
$ docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \
|
||||
docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \
|
||||
0.0.0.0:8080:192.168.4.2:8080 [...options...]
|
||||
```
|
||||
|
||||
You can also build onetun locally, using Rust ≥1.56:
|
||||
You can also build onetun locally, using Rust ≥1.80.0:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/aramperes/onetun && cd onetun
|
||||
$ cargo build --release
|
||||
$ ./target/release/onetun
|
||||
git clone https://github.com/aramperes/onetun && cd onetun
|
||||
cargo build --release
|
||||
./target/release/onetun
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
@ -54,7 +54,7 @@ access, or install any WireGuard tool on your local system for it to work.
|
|||
The only prerequisite is to register a peer IP and public key on the remote WireGuard endpoint; those are necessary for
|
||||
the WireGuard endpoint to trust the onetun peer and for packets to be routed.
|
||||
|
||||
```
|
||||
```shell
|
||||
onetun [src_host:]<src_port>:<dst_host>:<dst_port>[:TCP,UDP,...] [...] \
|
||||
--endpoint-addr <public WireGuard endpoint address> \
|
||||
--endpoint-public-key <the public key of the peer on the endpoint> \
|
||||
|
@ -70,7 +70,7 @@ onetun [src_host:]<src_port>:<dst_host>:<dst_port>[:TCP,UDP,...] [...] \
|
|||
|
||||
Suppose your WireGuard endpoint has the following configuration, and is accessible from `140.30.3.182:51820`:
|
||||
|
||||
```
|
||||
```shell
|
||||
# /etc/wireguard/wg0.conf
|
||||
|
||||
[Interface]
|
||||
|
@ -103,14 +103,14 @@ onetun 127.0.0.1:8080:192.168.4.2:8080 \
|
|||
|
||||
You'll then see this log:
|
||||
|
||||
```
|
||||
```shell
|
||||
INFO onetun > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
||||
Which means you can now access the port locally!
|
||||
|
||||
```
|
||||
$ curl 127.0.0.1:8080
|
||||
```shell
|
||||
curl 127.0.0.1:8080
|
||||
Hello world!
|
||||
```
|
||||
|
||||
|
@ -118,24 +118,32 @@ Hello world!
|
|||
|
||||
**onetun** supports running multiple tunnels in parallel. For example:
|
||||
|
||||
```
|
||||
$ onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081
|
||||
```shell
|
||||
onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081
|
||||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
||||
... would open TCP ports 8080 and 8081 locally, which forward to their respective ports on the different peers.
|
||||
|
||||
#### Maximum number of tunnels
|
||||
|
||||
`smoltcp` imposes a compile-time limit on the number of IP addresses assigned to an interface. **onetun** increases
|
||||
the default value to support most use-cases. In effect, the default limit on the number of **onetun** peers
|
||||
is **7 per protocol** (TCP and UDP).
|
||||
|
||||
Should you need more unique IP addresses to forward ports to, you can increase the limit in `.cargo/config.toml` and recompile **onetun**.
|
||||
|
||||
### UDP Support
|
||||
|
||||
**onetun** supports UDP forwarding. You can add `:UDP` at the end of the port-forward configuration, or `UDP,TCP` to support
|
||||
both protocols on the same port (note that this opens 2 separate tunnels, just on the same port)
|
||||
|
||||
```
|
||||
$ onetun 127.0.0.1:8080:192.168.4.2:8080:UDP
|
||||
```shell
|
||||
onetun 127.0.0.1:8080:192.168.4.2:8080:UDP
|
||||
INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
|
||||
$ onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP
|
||||
onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP
|
||||
INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
@ -147,16 +155,16 @@ it in any production capacity.
|
|||
|
||||
**onetun** supports both IPv4 and IPv6. In fact, you can use onetun to forward some IP version to another, e.g. 6-to-4:
|
||||
|
||||
```
|
||||
$ onetun [::1]:8080:192.168.4.2:8080
|
||||
```shell
|
||||
onetun [::1]:8080:192.168.4.2:8080
|
||||
INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
||||
Note that each tunnel can only support one "source" IP version and one "destination" IP version. If you want to support
|
||||
both IPv4 and IPv6 on the same port, you should create a second port-forward:
|
||||
|
||||
```
|
||||
$ onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080
|
||||
```shell
|
||||
onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080
|
||||
INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
@ -166,16 +174,33 @@ INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [
|
|||
For debugging purposes, you can enable the capture of IP packets sent between onetun and the WireGuard peer.
|
||||
The output is a libpcap capture file that can be viewed with Wireshark.
|
||||
|
||||
```
|
||||
$ onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080
|
||||
```shell
|
||||
onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080
|
||||
INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap
|
||||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
||||
To capture packets sent to and from the onetun local port, you must use an external tool like `tcpdump` with root access:
|
||||
|
||||
```shell
|
||||
sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8080'
|
||||
```
|
||||
$ sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8080'
|
||||
|
||||
### WireGuard Options
|
||||
|
||||
By default, onetun will create the UDP socket to communicate with the WireGuard endpoint on all interfaces and on a dynamic port,
|
||||
i.e. `0.0.0.0:0` for IPv4 endpoints, or `[::]:0` for IPv6.
|
||||
You can bind to a static address instead using `--endpoint-bind-addr`:
|
||||
|
||||
```shell
|
||||
onetun --endpoint-bind-addr 0.0.0.0:51820 --endpoint-addr 140.30.3.182:51820 [...]
|
||||
```
|
||||
|
||||
The security of the WireGuard connection can be further enhanced with a **pre-shared key** (PSK). You can generate such a key with the `wg genpsk` command, and provide it using `--preshared-key`.
|
||||
The peer must also have this key configured using the `PresharedKey` option.
|
||||
|
||||
```shell
|
||||
onetun --preshared-key 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' [...]
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
@ -242,6 +267,56 @@ if the least recently used port hasn't been used for a certain amount of time. I
|
|||
|
||||
All in all, I would not recommend using UDP forwarding for public services, since it's most likely prone to simple DoS or DDoS.
|
||||
|
||||
## HTTP/SOCKS Proxy
|
||||
|
||||
**onetun** is a Transport-layer proxy (also known as port forwarding); it is not in scope to provide
|
||||
a HTTP/SOCKS proxy server. However, you can easily chain **onetun** with a proxy server on a remote
|
||||
that is locked down to your WireGuard network.
|
||||
|
||||
For example, you could run [dante-server](https://www.inet.no/dante/) on a peer (ex. `192.168.4.2`) with the following configuration:
|
||||
|
||||
```
|
||||
# /etc/danted.conf
|
||||
|
||||
logoutput: syslog
|
||||
user.privileged: root
|
||||
user.unprivileged: nobody
|
||||
|
||||
internal: 192.168.4.2 port=1080
|
||||
external: eth0
|
||||
|
||||
socksmethod: none
|
||||
clientmethod: none
|
||||
|
||||
# Locks down proxy use to WireGuard peers (192.168.4.x)
|
||||
client pass {
|
||||
from: 192.168.4.0/24 to: 0.0.0.0/0
|
||||
}
|
||||
socks pass {
|
||||
from: 192.168.4.0/24 to: 0.0.0.0/0
|
||||
}
|
||||
```
|
||||
|
||||
Then use **onetun** to expose the SOCKS5 proxy locally:
|
||||
|
||||
```shell
|
||||
onetun 127.0.0.1:1080:192.168.4.2:1080
|
||||
INFO onetun::tunnel > Tunneling TCP [127.0.0.1:1080]->[192.168.4.2:1080] (via [140.30.3.182:51820] as peer 192.168.4.3)
|
||||
```
|
||||
|
||||
Test with `curl` (or configure your browser):
|
||||
|
||||
```shell
|
||||
curl -x socks5://127.0.0.1:1080 https://ifconfig.me
|
||||
```
|
||||
|
||||
## Contributing and Maintenance
|
||||
|
||||
I will gladly accept contributions to onetun, and set aside time to review all pull-requests.
|
||||
Please consider opening a GitHub issue if you are unsure if your contribution is within the scope of the project.
|
||||
|
||||
**Disclaimer**: I do not have enough personal time to actively maintain onetun besides open-source contributions.
|
||||
|
||||
## License
|
||||
|
||||
MIT License. See `LICENSE` for details. Copyright © 2021-2022 Aram Peres.
|
||||
MIT License. See `LICENSE` for details. Copyright © 2025 Aram Peres.
|
||||
|
|
229
src/config.rs
229
src/config.rs
|
@ -5,18 +5,19 @@ use std::fs::read_to_string;
|
|||
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use boringtun::crypto::{X25519PublicKey, X25519SecretKey};
|
||||
use anyhow::{bail, Context};
|
||||
pub use boringtun::x25519::{PublicKey, StaticSecret};
|
||||
|
||||
const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
pub port_forwards: Vec<PortForwardConfig>,
|
||||
#[allow(dead_code)]
|
||||
pub remote_port_forwards: Vec<PortForwardConfig>,
|
||||
pub private_key: Arc<X25519SecretKey>,
|
||||
pub endpoint_public_key: Arc<X25519PublicKey>,
|
||||
pub private_key: Arc<StaticSecret>,
|
||||
pub endpoint_public_key: Arc<PublicKey>,
|
||||
pub preshared_key: Option<[u8; 32]>,
|
||||
pub endpoint_addr: SocketAddr,
|
||||
pub endpoint_bind_addr: SocketAddr,
|
||||
pub source_peer_ip: IpAddr,
|
||||
|
@ -30,18 +31,17 @@ pub struct Config {
|
|||
impl Config {
|
||||
#[cfg(feature = "bin")]
|
||||
pub fn from_args() -> anyhow::Result<Self> {
|
||||
use clap::{App, Arg};
|
||||
use clap::{Arg, Command};
|
||||
|
||||
let mut warnings = vec![];
|
||||
|
||||
let matches = App::new("onetun")
|
||||
let matches = Command::new("onetun")
|
||||
.author("Aram Peres <aram.peres@gmail.com>")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.args(&[
|
||||
Arg::with_name("PORT_FORWARD")
|
||||
Arg::new("PORT_FORWARD")
|
||||
.required(false)
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.num_args(1..)
|
||||
.help("Port forward configurations. The format of each argument is [src_host:]<src_port>:<dst_host>:<dst_port>[:TCP,UDP,...], \
|
||||
where [src_host] is the local IP to listen on, <src_port> is the local port to listen on, <dst_host> is the remote peer IP to forward to, and <dst_port> is the remote port to forward to. \
|
||||
Environment variables of the form 'ONETUN_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1.\n\
|
||||
|
@ -55,74 +55,79 @@ impl Config {
|
|||
\tlocalhost:8080:192.168.4.1:8081:TCP\n\
|
||||
\tlocalhost:8080:peer.intranet:8081:TCP\
|
||||
"),
|
||||
Arg::with_name("private-key")
|
||||
.required_unless("private-key-file")
|
||||
.takes_value(true)
|
||||
Arg::new("private-key")
|
||||
.conflicts_with("private-key-file")
|
||||
.num_args(1)
|
||||
.long("private-key")
|
||||
.env("ONETUN_PRIVATE_KEY")
|
||||
.help("The private key of this peer. The corresponding public key should be registered in the WireGuard endpoint. \
|
||||
You can also use '--private-key-file' to specify a file containing the key instead."),
|
||||
Arg::with_name("private-key-file")
|
||||
.takes_value(true)
|
||||
Arg::new("private-key-file")
|
||||
.num_args(1)
|
||||
.long("private-key-file")
|
||||
.env("ONETUN_PRIVATE_KEY_FILE")
|
||||
.help("The path to a file containing the private key of this peer. The corresponding public key should be registered in the WireGuard endpoint."),
|
||||
Arg::with_name("endpoint-public-key")
|
||||
Arg::new("endpoint-public-key")
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("endpoint-public-key")
|
||||
.env("ONETUN_ENDPOINT_PUBLIC_KEY")
|
||||
.help("The public key of the WireGuard endpoint (remote)."),
|
||||
Arg::with_name("endpoint-addr")
|
||||
Arg::new("preshared-key")
|
||||
.required(false)
|
||||
.num_args(1)
|
||||
.long("preshared-key")
|
||||
.env("ONETUN_PRESHARED_KEY")
|
||||
.help("The pre-shared key (PSK) as configured with the peer."),
|
||||
Arg::new("endpoint-addr")
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("endpoint-addr")
|
||||
.env("ONETUN_ENDPOINT_ADDR")
|
||||
.help("The address (IP + port) of the WireGuard endpoint (remote). Example: 1.2.3.4:51820"),
|
||||
Arg::with_name("endpoint-bind-addr")
|
||||
Arg::new("endpoint-bind-addr")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("endpoint-bind-addr")
|
||||
.env("ONETUN_ENDPOINT_BIND_ADDR")
|
||||
.help("The address (IP + port) used to bind the local UDP socket for the WireGuard tunnel. Example: 1.2.3.4:30000. Defaults to 0.0.0.0:0 for IPv4 endpoints, or [::]:0 for IPv6 endpoints."),
|
||||
Arg::with_name("source-peer-ip")
|
||||
Arg::new("source-peer-ip")
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("source-peer-ip")
|
||||
.env("ONETUN_SOURCE_PEER_IP")
|
||||
.help("The source IP to identify this peer as (local). Example: 192.168.4.3"),
|
||||
Arg::with_name("keep-alive")
|
||||
Arg::new("keep-alive")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("keep-alive")
|
||||
.env("ONETUN_KEEP_ALIVE")
|
||||
.help("Configures a persistent keep-alive for the WireGuard tunnel, in seconds."),
|
||||
Arg::with_name("max-transmission-unit")
|
||||
Arg::new("max-transmission-unit")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("max-transmission-unit")
|
||||
.env("ONETUN_MTU")
|
||||
.default_value("1420")
|
||||
.help("Configures the max-transmission-unit (MTU) of the WireGuard tunnel."),
|
||||
Arg::with_name("log")
|
||||
Arg::new("log")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("log")
|
||||
.env("ONETUN_LOG")
|
||||
.default_value("info")
|
||||
.help("Configures the log level and format."),
|
||||
Arg::with_name("pcap")
|
||||
Arg::new("pcap")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.long("pcap")
|
||||
.env("ONETUN_PCAP")
|
||||
.help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file."),
|
||||
Arg::with_name("remote")
|
||||
Arg::new("remote")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.num_args(1..)
|
||||
.long("remote")
|
||||
.short("r")
|
||||
.short('r')
|
||||
.help("Remote port forward configurations. The format of each argument is <src_port>:<dst_host>:<dst_port>[:TCP,UDP,...], \
|
||||
where <src_port> is the port the other peers will reach the server with, <dst_host> is the IP to forward to, and <dst_port> is the port to forward to. \
|
||||
The <src_port> will be bound on onetun's peer IP, as specified by --source-peer-ip. If you pass a different value for <src_host> here, it will be rejected.\n\
|
||||
|
@ -137,7 +142,7 @@ impl Config {
|
|||
|
||||
// Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs
|
||||
let mut port_forward_strings = HashSet::new();
|
||||
if let Some(values) = matches.values_of("PORT_FORWARD") {
|
||||
if let Some(values) = matches.get_many::<String>("PORT_FORWARD") {
|
||||
for value in values {
|
||||
port_forward_strings.insert(value.to_owned());
|
||||
}
|
||||
|
@ -156,18 +161,18 @@ impl Config {
|
|||
.map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE))
|
||||
.collect();
|
||||
let port_forwards: Vec<PortForwardConfig> = port_forwards
|
||||
.with_context(|| "Failed to parse port forward config")?
|
||||
.context("Failed to parse port forward config")?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
// Read source-peer-ip
|
||||
let source_peer_ip = parse_ip(matches.value_of("source-peer-ip"))
|
||||
.with_context(|| "Invalid source peer IP")?;
|
||||
let source_peer_ip = parse_ip(matches.get_one::<String>("source-peer-ip"))
|
||||
.context("Invalid source peer IP")?;
|
||||
|
||||
// Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs
|
||||
let mut port_forward_strings = HashSet::new();
|
||||
if let Some(values) = matches.values_of("remote") {
|
||||
if let Some(values) = matches.get_many::<String>("remote") {
|
||||
for value in values {
|
||||
port_forward_strings.insert(value.to_owned());
|
||||
}
|
||||
|
@ -186,30 +191,30 @@ impl Config {
|
|||
.map(|s| {
|
||||
PortForwardConfig::from_notation(
|
||||
&s,
|
||||
matches.value_of("source-peer-ip").unwrap(),
|
||||
matches.get_one::<String>("source-peer-ip").unwrap(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
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()
|
||||
.flatten()
|
||||
.collect();
|
||||
for port_forward in remote_port_forwards.iter_mut() {
|
||||
if port_forward.source.ip() != source_peer_ip {
|
||||
return Err(anyhow::anyhow!("Remote port forward config <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.remote = true;
|
||||
}
|
||||
|
||||
if port_forwards.is_empty() && remote_port_forwards.is_empty() {
|
||||
return Err(anyhow::anyhow!("No port forward configurations given."));
|
||||
bail!("No port forward configurations given.");
|
||||
}
|
||||
|
||||
// Read private key from file or CLI argument
|
||||
let (group_readable, world_readable) = matches
|
||||
.value_of("private-key-file")
|
||||
.get_one::<String>("private-key-file")
|
||||
.and_then(is_file_insecurely_readable)
|
||||
.unwrap_or_default();
|
||||
if group_readable {
|
||||
|
@ -219,31 +224,32 @@ impl Config {
|
|||
warnings.push("Private key file is world-readable. This is insecure.".into());
|
||||
}
|
||||
|
||||
let private_key = if let Some(private_key_file) = matches.value_of("private-key-file") {
|
||||
let private_key = if let Some(private_key_file) =
|
||||
matches.get_one::<String>("private-key-file")
|
||||
{
|
||||
read_to_string(private_key_file)
|
||||
.map(|s| s.trim().to_string())
|
||||
.with_context(|| "Failed to read private key file")
|
||||
.context("Failed to read private key file")
|
||||
} else {
|
||||
if std::env::var("ONETUN_PRIVATE_KEY").is_err() {
|
||||
warnings.push("Private key was passed using CLI. This is insecure. \
|
||||
Use \"--private-key-file <file containing private key>\", or the \"ONETUN_PRIVATE_KEY\" env variable instead.".into());
|
||||
}
|
||||
matches
|
||||
.value_of("private-key")
|
||||
.map(String::from)
|
||||
.with_context(|| "Missing private key")
|
||||
.get_one::<String>("private-key")
|
||||
.cloned()
|
||||
.context("Missing private key")
|
||||
}?;
|
||||
|
||||
let endpoint_addr = parse_addr(matches.value_of("endpoint-addr"))
|
||||
.with_context(|| "Invalid endpoint address")?;
|
||||
let endpoint_addr = parse_addr(matches.get_one::<String>("endpoint-addr"))
|
||||
.context("Invalid endpoint address")?;
|
||||
|
||||
let endpoint_bind_addr = if let Some(addr) = matches.value_of("endpoint-bind-addr") {
|
||||
let addr = parse_addr(Some(addr)).with_context(|| "Invalid bind address")?;
|
||||
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")?;
|
||||
// Make sure the bind address and endpoint address are the same IP version
|
||||
if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Endpoint and bind addresses must be the same IP version"
|
||||
));
|
||||
bail!("Endpoint and bind addresses must be the same IP version");
|
||||
}
|
||||
addr
|
||||
} else {
|
||||
|
@ -257,54 +263,77 @@ impl Config {
|
|||
Ok(Self {
|
||||
port_forwards,
|
||||
remote_port_forwards,
|
||||
private_key: Arc::new(
|
||||
parse_private_key(&private_key).with_context(|| "Invalid private key")?,
|
||||
),
|
||||
private_key: Arc::new(parse_private_key(&private_key).context("Invalid private key")?),
|
||||
endpoint_public_key: Arc::new(
|
||||
parse_public_key(matches.value_of("endpoint-public-key"))
|
||||
.with_context(|| "Invalid endpoint public key")?,
|
||||
parse_public_key(matches.get_one::<String>("endpoint-public-key"))
|
||||
.context("Invalid endpoint public key")?,
|
||||
),
|
||||
preshared_key: parse_preshared_key(matches.get_one::<String>("preshared-key"))?,
|
||||
endpoint_addr,
|
||||
endpoint_bind_addr,
|
||||
source_peer_ip,
|
||||
keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive"))
|
||||
.with_context(|| "Invalid keep-alive value")?,
|
||||
max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit"))
|
||||
.with_context(|| "Invalid max-transmission-unit value")?,
|
||||
log: matches.value_of("log").unwrap_or_default().into(),
|
||||
pcap_file: matches.value_of("pcap").map(String::from),
|
||||
keepalive_seconds: parse_keep_alive(matches.get_one::<String>("keep-alive"))
|
||||
.context("Invalid keep-alive value")?,
|
||||
max_transmission_unit: parse_mtu(matches.get_one::<String>("max-transmission-unit"))
|
||||
.context("Invalid max-transmission-unit value")?,
|
||||
log: matches
|
||||
.get_one::<String>("log")
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
pcap_file: matches.get_one::<String>("pcap").cloned(),
|
||||
warnings,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_addr(s: Option<&str>) -> anyhow::Result<SocketAddr> {
|
||||
s.with_context(|| "Missing address")?
|
||||
fn parse_addr<T: AsRef<str>>(s: Option<T>) -> anyhow::Result<SocketAddr> {
|
||||
s.context("Missing address")?
|
||||
.as_ref()
|
||||
.to_socket_addrs()
|
||||
.with_context(|| "Invalid address")?
|
||||
.context("Invalid address")?
|
||||
.next()
|
||||
.with_context(|| "Could not lookup address")
|
||||
.context("Could not lookup address")
|
||||
}
|
||||
|
||||
fn parse_ip(s: Option<&str>) -> anyhow::Result<IpAddr> {
|
||||
s.with_context(|| "Missing IP")?
|
||||
fn parse_ip(s: Option<&String>) -> anyhow::Result<IpAddr> {
|
||||
s.context("Missing IP address")?
|
||||
.parse::<IpAddr>()
|
||||
.with_context(|| "Invalid IP address")
|
||||
.context("Invalid IP address")
|
||||
}
|
||||
|
||||
fn parse_private_key(s: &str) -> anyhow::Result<X25519SecretKey> {
|
||||
s.parse::<X25519SecretKey>()
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))
|
||||
fn parse_private_key(s: &str) -> anyhow::Result<StaticSecret> {
|
||||
let decoded = base64::decode(s).context("Failed to decode private key")?;
|
||||
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() {
|
||||
Ok(StaticSecret::from(bytes))
|
||||
} else {
|
||||
bail!("Invalid private key")
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_public_key(s: Option<&str>) -> anyhow::Result<X25519PublicKey> {
|
||||
s.with_context(|| "Missing public key")?
|
||||
.parse::<X25519PublicKey>()
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))
|
||||
.with_context(|| "Invalid public key")
|
||||
fn parse_public_key(s: Option<&String>) -> anyhow::Result<PublicKey> {
|
||||
let encoded = s.context("Missing public key")?;
|
||||
let decoded = base64::decode(encoded).context("Failed to decode public key")?;
|
||||
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() {
|
||||
Ok(PublicKey::from(bytes))
|
||||
} else {
|
||||
bail!("Invalid public key")
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_keep_alive(s: Option<&str>) -> anyhow::Result<Option<u16>> {
|
||||
fn parse_preshared_key(s: Option<&String>) -> anyhow::Result<Option<[u8; 32]>> {
|
||||
if let Some(s) = s {
|
||||
let decoded = base64::decode(s).context("Failed to decode preshared key")?;
|
||||
if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() {
|
||||
Ok(Some(bytes))
|
||||
} else {
|
||||
bail!("Invalid preshared key")
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_keep_alive(s: Option<&String>) -> anyhow::Result<Option<u16>> {
|
||||
if let Some(s) = s {
|
||||
let parsed: u16 = s.parse().with_context(|| {
|
||||
format!(
|
||||
|
@ -318,23 +347,21 @@ fn parse_keep_alive(s: Option<&str>) -> anyhow::Result<Option<u16>> {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_mtu(s: Option<&str>) -> anyhow::Result<usize> {
|
||||
s.with_context(|| "Missing MTU")?
|
||||
.parse()
|
||||
.with_context(|| "Invalid MTU")
|
||||
fn parse_mtu(s: Option<&String>) -> anyhow::Result<usize> {
|
||||
s.context("Missing MTU")?.parse().context("Invalid MTU")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_file_insecurely_readable(path: &str) -> Option<(bool, bool)> {
|
||||
fn is_file_insecurely_readable(path: &String) -> Option<(bool, bool)> {
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
let mode = File::open(&path).ok()?.metadata().ok()?.mode();
|
||||
let mode = File::open(path).ok()?.metadata().ok()?.mode();
|
||||
Some((mode & 0o40 > 0, mode & 0o4 > 0))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn is_file_insecurely_readable(_path: &str) -> Option<(bool, bool)> {
|
||||
fn is_file_insecurely_readable(_path: &String) -> Option<(bool, bool)> {
|
||||
// No good way to determine permissions on non-Unix target
|
||||
None
|
||||
}
|
||||
|
@ -450,27 +477,21 @@ impl PortForwardConfig {
|
|||
|
||||
let source = (
|
||||
src_addr.0.unwrap_or(default_source),
|
||||
src_addr
|
||||
.1
|
||||
.parse::<u16>()
|
||||
.with_context(|| "Invalid source port")?,
|
||||
src_addr.1.parse::<u16>().context("Invalid source port")?,
|
||||
)
|
||||
.to_socket_addrs()
|
||||
.with_context(|| "Invalid source address")?
|
||||
.context("Invalid source address")?
|
||||
.next()
|
||||
.with_context(|| "Could not resolve source address")?;
|
||||
.context("Could not resolve source address")?;
|
||||
|
||||
let destination = (
|
||||
dst_addr.0,
|
||||
dst_addr
|
||||
.1
|
||||
.parse::<u16>()
|
||||
.with_context(|| "Invalid source port")?,
|
||||
dst_addr.1.parse::<u16>().context("Invalid source port")?,
|
||||
)
|
||||
.to_socket_addrs() // TODO: Pass this as given and use DNS config instead (issue #15)
|
||||
.with_context(|| "Invalid destination address")?
|
||||
.context("Invalid destination address")?
|
||||
.next()
|
||||
.with_context(|| "Could not resolve destination address")?;
|
||||
.context("Could not resolve destination address")?;
|
||||
|
||||
// Parse protocols
|
||||
let protocols = if let Some(protocols) = protocols {
|
||||
|
@ -480,7 +501,7 @@ impl PortForwardConfig {
|
|||
} else {
|
||||
Ok(vec![PortProtocol::Tcp])
|
||||
}
|
||||
.with_context(|| "Failed to parse protocols")?;
|
||||
.context("Failed to parse protocols")?;
|
||||
|
||||
// Returns an config for each protocol
|
||||
Ok(protocols
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use bytes::Bytes;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
@ -16,13 +17,13 @@ pub enum Event {
|
|||
/// A connection was dropped from the pool and should be closed in all interfaces.
|
||||
ClientConnectionDropped(VirtualPort),
|
||||
/// Data received by the local server that should be sent to the virtual server.
|
||||
LocalData(PortForwardConfig, VirtualPort, Vec<u8>),
|
||||
LocalData(PortForwardConfig, VirtualPort, Bytes),
|
||||
/// Data received by the remote server that should be sent to the local client.
|
||||
RemoteData(VirtualPort, Vec<u8>),
|
||||
RemoteData(VirtualPort, Bytes),
|
||||
/// IP packet received from the WireGuard tunnel that should be passed through the corresponding virtual device.
|
||||
InboundInternetPacket(PortProtocol, Vec<u8>),
|
||||
InboundInternetPacket(PortProtocol, Bytes),
|
||||
/// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device.
|
||||
OutboundInternetPacket(Vec<u8>),
|
||||
OutboundInternetPacket(Bytes),
|
||||
/// Notifies that a virtual device read an IP packet.
|
||||
VirtualDeviceFed(PortProtocol),
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> {
|
|||
|
||||
let wg = WireGuardTunnel::new(&config, bus.clone())
|
||||
.await
|
||||
.with_context(|| "Failed to initialize WireGuard tunnel")?;
|
||||
.context("Failed to initialize WireGuard tunnel")?;
|
||||
let wg = Arc::new(wg);
|
||||
|
||||
{
|
||||
|
@ -53,7 +53,7 @@ pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> {
|
|||
{
|
||||
// Start consumption task for WireGuard
|
||||
let wg = wg.clone();
|
||||
tokio::spawn(async move { wg.consume_task().await });
|
||||
tokio::spawn(Box::pin(async move { wg.consume_task().await }));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
use anyhow::Context;
|
||||
use onetun::{config::Config, events::Bus};
|
||||
|
||||
let config = Config::from_args().with_context(|| "Failed to read config")?;
|
||||
let config = Config::from_args().context("Configuration has errors")?;
|
||||
init_logger(&config)?;
|
||||
|
||||
for warning in &config.warnings {
|
||||
|
@ -32,7 +32,5 @@ fn init_logger(config: &onetun::config::Config) -> anyhow::Result<()> {
|
|||
|
||||
let mut builder = pretty_env_logger::formatted_timed_builder();
|
||||
builder.parse_filters(&config.log);
|
||||
builder
|
||||
.try_init()
|
||||
.with_context(|| "Failed to initialize logger")
|
||||
builder.try_init().context("Failed to initialize logger")
|
||||
}
|
||||
|
|
20
src/pcap.rs
20
src/pcap.rs
|
@ -16,7 +16,7 @@ impl Pcap {
|
|||
self.writer
|
||||
.flush()
|
||||
.await
|
||||
.with_context(|| "Failed to flush pcap writer")
|
||||
.context("Failed to flush pcap writer")
|
||||
}
|
||||
|
||||
async fn write(&mut self, data: &[u8]) -> anyhow::Result<usize> {
|
||||
|
@ -30,14 +30,14 @@ impl Pcap {
|
|||
self.writer
|
||||
.write_u16(value)
|
||||
.await
|
||||
.with_context(|| "Failed to write u16 to pcap writer")
|
||||
.context("Failed to write u16 to pcap writer")
|
||||
}
|
||||
|
||||
async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> {
|
||||
self.writer
|
||||
.write_u32(value)
|
||||
.await
|
||||
.with_context(|| "Failed to write u32 to pcap writer")
|
||||
.context("Failed to write u32 to pcap writer")
|
||||
}
|
||||
|
||||
async fn global_header(&mut self) -> anyhow::Result<()> {
|
||||
|
@ -64,14 +64,14 @@ impl Pcap {
|
|||
async fn packet(&mut self, timestamp: Instant, packet: &[u8]) -> anyhow::Result<()> {
|
||||
self.packet_header(timestamp, packet.len())
|
||||
.await
|
||||
.with_context(|| "Failed to write packet header to pcap writer")?;
|
||||
.context("Failed to write packet header to pcap writer")?;
|
||||
self.write(packet)
|
||||
.await
|
||||
.with_context(|| "Failed to write packet to pcap writer")?;
|
||||
.context("Failed to write packet to pcap writer")?;
|
||||
self.writer
|
||||
.flush()
|
||||
.await
|
||||
.with_context(|| "Failed to flush pcap writer")?;
|
||||
.context("Failed to flush pcap writer")?;
|
||||
self.flush().await
|
||||
}
|
||||
}
|
||||
|
@ -81,14 +81,14 @@ pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> {
|
|||
let mut endpoint = bus.new_endpoint();
|
||||
let file = File::create(&pcap_file)
|
||||
.await
|
||||
.with_context(|| "Failed to create pcap file")?;
|
||||
.context("Failed to create pcap file")?;
|
||||
let writer = BufWriter::new(file);
|
||||
|
||||
let mut writer = Pcap { writer };
|
||||
writer
|
||||
.global_header()
|
||||
.await
|
||||
.with_context(|| "Failed to write global header to pcap writer")?;
|
||||
.context("Failed to write global header to pcap writer")?;
|
||||
|
||||
info!("Capturing WireGuard IP packets to {}", &pcap_file);
|
||||
loop {
|
||||
|
@ -98,14 +98,14 @@ pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> {
|
|||
writer
|
||||
.packet(instant, &ip)
|
||||
.await
|
||||
.with_context(|| "Failed to write inbound IP packet to pcap writer")?;
|
||||
.context("Failed to write inbound IP packet to pcap writer")?;
|
||||
}
|
||||
Event::OutboundInternetPacket(ip) => {
|
||||
let instant = Instant::now();
|
||||
writer
|
||||
.packet(instant, &ip)
|
||||
.await
|
||||
.with_context(|| "Failed to write output IP packet to pcap writer")?;
|
||||
.context("Failed to write output IP packet to pcap writer")?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
use crate::config::{PortForwardConfig, PortProtocol};
|
||||
use crate::virtual_iface::VirtualPort;
|
||||
use anyhow::Context;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::events::{Bus, Event};
|
||||
use anyhow::Context;
|
||||
use bytes::BytesMut;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use crate::config::{PortForwardConfig, PortProtocol};
|
||||
use crate::events::{Bus, Event};
|
||||
use crate::virtual_iface::VirtualPort;
|
||||
|
||||
const MAX_PACKET: usize = 65536;
|
||||
const MIN_PORT: u16 = 1000;
|
||||
|
@ -26,14 +27,14 @@ pub async fn tcp_proxy_server(
|
|||
) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(port_forward.source)
|
||||
.await
|
||||
.with_context(|| "Failed to listen on TCP proxy server")?;
|
||||
.context("Failed to listen on TCP proxy server")?;
|
||||
|
||||
loop {
|
||||
let port_pool = port_pool.clone();
|
||||
let (socket, peer_addr) = listener
|
||||
.accept()
|
||||
.await
|
||||
.with_context(|| "Failed to accept connection on TCP proxy server")?;
|
||||
.context("Failed to accept connection on TCP proxy server")?;
|
||||
|
||||
// Assign a 'virtual port': this is a unique port number used to route IP packets
|
||||
// received from the WireGuard tunnel. It is the port number that the virtual client will
|
||||
|
@ -81,7 +82,7 @@ async fn handle_tcp_proxy_connection(
|
|||
let mut endpoint = bus.new_endpoint();
|
||||
endpoint.send(Event::ClientConnectionInitiated(port_forward, virtual_port));
|
||||
|
||||
let mut buffer = Vec::with_capacity(MAX_PACKET);
|
||||
let mut buffer = BytesMut::with_capacity(MAX_PACKET);
|
||||
loop {
|
||||
tokio::select! {
|
||||
readable_result = socket.readable() => {
|
||||
|
@ -90,7 +91,7 @@ async fn handle_tcp_proxy_connection(
|
|||
match socket.try_read_buf(&mut buffer) {
|
||||
Ok(size) if size > 0 => {
|
||||
let data = Vec::from(&buffer[..size]);
|
||||
endpoint.send(Event::LocalData(port_forward, virtual_port, data));
|
||||
endpoint.send(Event::LocalData(port_forward, virtual_port, data.into()));
|
||||
// Reset buffer
|
||||
buffer.clear();
|
||||
}
|
||||
|
@ -185,13 +186,13 @@ impl TcpPortPool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity).
|
||||
/// Requests a free port from the pool. An error is returned if none is available (exhausted max capacity).
|
||||
pub async fn next(&self) -> anyhow::Result<VirtualPort> {
|
||||
let mut inner = self.inner.write().await;
|
||||
let port = inner
|
||||
.queue
|
||||
.pop_front()
|
||||
.with_context(|| "TCP virtual port pool is exhausted")?;
|
||||
.context("TCP virtual port pool is exhausted")?;
|
||||
Ok(VirtualPort::new(port, PortProtocol::Tcp))
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,15 @@ use std::ops::Range;
|
|||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::events::{Bus, Event};
|
||||
use anyhow::Context;
|
||||
use bytes::Bytes;
|
||||
use priority_queue::double_priority_queue::DoublePriorityQueue;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use crate::config::{PortForwardConfig, PortProtocol};
|
||||
use crate::events::{Bus, Event};
|
||||
use crate::virtual_iface::VirtualPort;
|
||||
|
||||
const MAX_PACKET: usize = 65536;
|
||||
|
@ -36,7 +37,7 @@ pub async fn udp_proxy_server(
|
|||
let mut endpoint = bus.new_endpoint();
|
||||
let socket = UdpSocket::bind(port_forward.source)
|
||||
.await
|
||||
.with_context(|| "Failed to bind on UDP proxy address")?;
|
||||
.context("Failed to bind on UDP proxy address")?;
|
||||
|
||||
let mut buffer = [0u8; MAX_PACKET];
|
||||
loop {
|
||||
|
@ -98,11 +99,11 @@ async fn next_udp_datagram(
|
|||
socket: &UdpSocket,
|
||||
buffer: &mut [u8],
|
||||
port_pool: UdpPortPool,
|
||||
) -> anyhow::Result<Option<(VirtualPort, Vec<u8>)>> {
|
||||
) -> anyhow::Result<Option<(VirtualPort, Bytes)>> {
|
||||
let (size, peer_addr) = socket
|
||||
.recv_from(buffer)
|
||||
.await
|
||||
.with_context(|| "Failed to accept incoming UDP datagram")?;
|
||||
.context("Failed to accept incoming UDP datagram")?;
|
||||
|
||||
// Assign a 'virtual port': this is a unique port number used to route IP packets
|
||||
// received from the WireGuard tunnel. It is the port number that the virtual client will
|
||||
|
@ -126,7 +127,7 @@ async fn next_udp_datagram(
|
|||
port_pool.update_last_transmit(port).await;
|
||||
|
||||
let data = buffer[..size].to_vec();
|
||||
Ok(Some((port, data)))
|
||||
Ok(Some((port, data.into())))
|
||||
}
|
||||
|
||||
/// A pool of virtual ports available for TCP connections.
|
||||
|
@ -211,7 +212,7 @@ impl UdpPortPool {
|
|||
None
|
||||
}
|
||||
})
|
||||
.with_context(|| "virtual port pool is exhausted")?;
|
||||
.context("Virtual port pool is exhausted")?;
|
||||
|
||||
inner.port_by_peer_addr.insert(peer_addr, port);
|
||||
inner.peer_addr_by_port.insert(port, peer_addr);
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
use crate::config::PortProtocol;
|
||||
use crate::events::{BusSender, Event};
|
||||
use crate::Bus;
|
||||
use smoltcp::phy::{Device, DeviceCapabilities, Medium};
|
||||
use smoltcp::time::Instant;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use smoltcp::{
|
||||
phy::{DeviceCapabilities, Medium},
|
||||
time::Instant,
|
||||
};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// A virtual device that processes IP packets through smoltcp and WireGuard.
|
||||
pub struct VirtualIpDevice {
|
||||
|
@ -13,7 +18,7 @@ pub struct VirtualIpDevice {
|
|||
/// Channel receiver for received IP packets.
|
||||
bus_sender: BusSender,
|
||||
/// Local queue for packets received from the bus that need to go through the smoltcp interface.
|
||||
process_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
|
||||
process_queue: Arc<Mutex<VecDeque<Bytes>>>,
|
||||
}
|
||||
|
||||
impl VirtualIpDevice {
|
||||
|
@ -49,11 +54,17 @@ impl VirtualIpDevice {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Device<'a> for VirtualIpDevice {
|
||||
type RxToken = RxToken;
|
||||
type TxToken = TxToken;
|
||||
impl smoltcp::phy::Device for VirtualIpDevice {
|
||||
type RxToken<'a>
|
||||
= RxToken
|
||||
where
|
||||
Self: 'a;
|
||||
type TxToken<'a>
|
||||
= TxToken
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
|
||||
let next = {
|
||||
let mut queue = self
|
||||
.process_queue
|
||||
|
@ -63,7 +74,13 @@ impl<'a> Device<'a> for VirtualIpDevice {
|
|||
};
|
||||
match next {
|
||||
Some(buffer) => Some((
|
||||
Self::RxToken { buffer },
|
||||
Self::RxToken {
|
||||
buffer: {
|
||||
let mut buf = BytesMut::new();
|
||||
buf.put(buffer);
|
||||
buf
|
||||
},
|
||||
},
|
||||
Self::TxToken {
|
||||
sender: self.bus_sender.clone(),
|
||||
},
|
||||
|
@ -72,7 +89,7 @@ impl<'a> Device<'a> for VirtualIpDevice {
|
|||
}
|
||||
}
|
||||
|
||||
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
||||
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
|
||||
Some(TxToken {
|
||||
sender: self.bus_sender.clone(),
|
||||
})
|
||||
|
@ -88,15 +105,15 @@ impl<'a> Device<'a> for VirtualIpDevice {
|
|||
|
||||
#[doc(hidden)]
|
||||
pub struct RxToken {
|
||||
buffer: Vec<u8>,
|
||||
buffer: BytesMut,
|
||||
}
|
||||
|
||||
impl smoltcp::phy::RxToken for RxToken {
|
||||
fn consume<R, F>(mut self, _timestamp: Instant, f: F) -> smoltcp::Result<R>
|
||||
fn consume<R, F>(self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
|
||||
F: FnOnce(&[u8]) -> R,
|
||||
{
|
||||
f(&mut self.buffer)
|
||||
f(&self.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,14 +123,14 @@ pub struct TxToken {
|
|||
}
|
||||
|
||||
impl smoltcp::phy::TxToken for TxToken {
|
||||
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result<R>
|
||||
fn consume<R, F>(self, len: usize, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
|
||||
F: FnOnce(&mut [u8]) -> R,
|
||||
{
|
||||
let mut buffer = Vec::new();
|
||||
buffer.resize(len, 0);
|
||||
let mut buffer = vec![0; len];
|
||||
let result = f(&mut buffer);
|
||||
self.sender.send(Event::OutboundInternetPacket(buffer));
|
||||
self.sender
|
||||
.send(Event::OutboundInternetPacket(buffer.into()));
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,19 @@ use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort};
|
|||
use crate::Bus;
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use smoltcp::iface::{InterfaceBuilder, SocketHandle};
|
||||
use smoltcp::socket::{TcpSocket, TcpSocketBuffer, TcpState};
|
||||
use smoltcp::wire::{IpAddress, IpCidr};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
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;
|
||||
|
||||
|
@ -19,6 +26,7 @@ pub struct TcpVirtualInterface {
|
|||
source_peer_ip: IpAddr,
|
||||
port_forwards: Vec<PortForwardConfig>,
|
||||
bus: Bus,
|
||||
sockets: SocketSet<'static>,
|
||||
}
|
||||
|
||||
impl TcpVirtualInterface {
|
||||
|
@ -32,33 +40,34 @@ impl TcpVirtualInterface {
|
|||
.collect(),
|
||||
source_peer_ip,
|
||||
bus,
|
||||
sockets: SocketSet::new([]),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<TcpSocket<'static>> {
|
||||
fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<tcp::Socket<'static>> {
|
||||
static mut TCP_SERVER_RX_DATA: [u8; 0] = [];
|
||||
static mut TCP_SERVER_TX_DATA: [u8; 0] = [];
|
||||
|
||||
let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] });
|
||||
let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] });
|
||||
let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
||||
let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] });
|
||||
let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] });
|
||||
let mut socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
|
||||
|
||||
socket
|
||||
.listen((
|
||||
IpAddress::from(port_forward.destination.ip()),
|
||||
port_forward.destination.port(),
|
||||
))
|
||||
.with_context(|| "Virtual server socket failed to listen")?;
|
||||
.context("Virtual server socket failed to listen")?;
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
fn new_client_socket() -> anyhow::Result<TcpSocket<'static>> {
|
||||
fn new_client_socket() -> anyhow::Result<tcp::Socket<'static>> {
|
||||
let rx_data = vec![0u8; MAX_PACKET];
|
||||
let tx_data = vec![0u8; MAX_PACKET];
|
||||
let tcp_rx_buffer = TcpSocketBuffer::new(rx_data);
|
||||
let tcp_tx_buffer = TcpSocketBuffer::new(tx_data);
|
||||
let socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
||||
let tcp_rx_buffer = tcp::SocketBuffer::new(rx_data);
|
||||
let tcp_tx_buffer = tcp::SocketBuffer::new(tx_data);
|
||||
let socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
|
@ -70,26 +79,32 @@ impl TcpVirtualInterface {
|
|||
}
|
||||
addresses
|
||||
.into_iter()
|
||||
.map(|addr| IpCidr::new(addr, 32))
|
||||
.map(|addr| IpCidr::new(addr, addr_length(&addr)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl VirtualInterfacePoll for TcpVirtualInterface {
|
||||
async fn poll_loop(self, 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
|
||||
let addresses = self.addresses();
|
||||
let config = Config::new(HardwareAddress::Ip);
|
||||
|
||||
// Create virtual interface (contains smoltcp state machine)
|
||||
let mut iface = InterfaceBuilder::new(device, vec![])
|
||||
.ip_addrs(addresses)
|
||||
.finalize();
|
||||
let mut iface = Interface::new(config, &mut device, Instant::now());
|
||||
iface.update_ip_addrs(|ip_addrs| {
|
||||
addresses.into_iter().for_each(|addr| {
|
||||
ip_addrs
|
||||
.push(addr)
|
||||
.expect("maximum number of IPs in TCP interface reached");
|
||||
});
|
||||
});
|
||||
|
||||
// Create virtual server for each port forward
|
||||
for port_forward in self.port_forwards.iter() {
|
||||
let server_socket = TcpVirtualInterface::new_server_socket(*port_forward)?;
|
||||
iface.add_socket(server_socket);
|
||||
self.sockets.add(server_socket);
|
||||
}
|
||||
|
||||
// The next time to poll the interface. Can be None for instant poll.
|
||||
|
@ -102,7 +117,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
let mut port_client_handle_map: HashMap<VirtualPort, SocketHandle> = HashMap::new();
|
||||
|
||||
// Data packets to send from a virtual client
|
||||
let mut send_queue: HashMap<VirtualPort, VecDeque<Vec<u8>>> = HashMap::new();
|
||||
let mut send_queue: HashMap<VirtualPort, VecDeque<Bytes>> = HashMap::new();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
|
@ -115,11 +130,11 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
|
||||
// Find closed sockets
|
||||
port_client_handle_map.retain(|virtual_port, client_handle| {
|
||||
let client_socket = iface.get_socket::<TcpSocket>(*client_handle);
|
||||
if client_socket.state() == TcpState::Closed {
|
||||
let client_socket = self.sockets.get_mut::<tcp::Socket>(*client_handle);
|
||||
if client_socket.state() == tcp::State::Closed {
|
||||
endpoint.send(Event::ClientConnectionDropped(*virtual_port));
|
||||
send_queue.remove(virtual_port);
|
||||
iface.remove_socket(*client_handle);
|
||||
self.sockets.remove(*client_handle);
|
||||
false
|
||||
} else {
|
||||
// Not closed, retain
|
||||
|
@ -127,16 +142,12 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
}
|
||||
});
|
||||
|
||||
match iface.poll(loop_start) {
|
||||
Ok(processed) if processed => {
|
||||
trace!("TCP virtual interface polled some packets to be processed");
|
||||
}
|
||||
Err(e) => error!("TCP virtual interface poll error: {:?}", e),
|
||||
_ => {}
|
||||
if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged {
|
||||
log::trace!("TCP virtual interface polled some packets to be processed");
|
||||
}
|
||||
|
||||
for (virtual_port, client_handle) in port_client_handle_map.iter() {
|
||||
let client_socket = iface.get_socket::<TcpSocket>(*client_handle);
|
||||
let client_socket = self.sockets.get_mut::<tcp::Socket>(*client_handle);
|
||||
if client_socket.can_send() {
|
||||
if let Some(send_queue) = send_queue.get_mut(virtual_port) {
|
||||
let to_transfer = send_queue.pop_front();
|
||||
|
@ -147,7 +158,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
if sent < total {
|
||||
// Sometimes only a subset is sent, so the rest needs to be sent on the next poll
|
||||
let tx_extra = Vec::from(&to_transfer_slice[sent..total]);
|
||||
send_queue.push_front(tx_extra);
|
||||
send_queue.push_front(tx_extra.into());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -156,13 +167,13 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
);
|
||||
}
|
||||
}
|
||||
} else if client_socket.state() == TcpState::CloseWait {
|
||||
} else if client_socket.state() == tcp::State::CloseWait {
|
||||
client_socket.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
if client_socket.can_recv() {
|
||||
match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) {
|
||||
match client_socket.recv(|buffer| (buffer.len(), Bytes::from(buffer.to_vec()))) {
|
||||
Ok(data) => {
|
||||
debug!("[{}] Received {} bytes from virtual server", virtual_port, data.len());
|
||||
if !data.is_empty() {
|
||||
|
@ -179,7 +190,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
}
|
||||
|
||||
// The virtual interface determines the next time to poll (this is to reduce unnecessary polls)
|
||||
next_poll = match iface.poll_delay(loop_start) {
|
||||
next_poll = match iface.poll_delay(loop_start, &self.sockets) {
|
||||
Some(smoltcp::time::Duration::ZERO) => None,
|
||||
Some(delay) => {
|
||||
trace!("TCP Virtual interface delayed next poll by {}", delay);
|
||||
|
@ -192,13 +203,14 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
match event {
|
||||
Event::ClientConnectionInitiated(port_forward, virtual_port) => {
|
||||
let client_socket = TcpVirtualInterface::new_client_socket()?;
|
||||
let client_handle = iface.add_socket(client_socket);
|
||||
let client_handle = self.sockets.add(client_socket);
|
||||
|
||||
// Add handle to map
|
||||
port_client_handle_map.insert(virtual_port, client_handle);
|
||||
send_queue.insert(virtual_port, VecDeque::new());
|
||||
|
||||
let (client_socket, context) = iface.get_socket_and_context::<TcpSocket>(client_handle);
|
||||
let client_socket = self.sockets.get_mut::<tcp::Socket>(client_handle);
|
||||
let context = iface.context();
|
||||
|
||||
client_socket
|
||||
.connect(
|
||||
|
@ -209,13 +221,13 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
),
|
||||
(IpAddress::from(self.source_peer_ip), virtual_port.num()),
|
||||
)
|
||||
.with_context(|| "Virtual server socket failed to listen")?;
|
||||
.context("Virtual server socket failed to listen")?;
|
||||
|
||||
next_poll = None;
|
||||
}
|
||||
Event::ClientConnectionDropped(virtual_port) => {
|
||||
if let Some(client_handle) = port_client_handle_map.get(&virtual_port) {
|
||||
let client_socket = iface.get_socket::<TcpSocket>(*client_handle);
|
||||
let client_socket = self.sockets.get_mut::<tcp::Socket>(*client_handle);
|
||||
client_socket.close();
|
||||
next_poll = None;
|
||||
}
|
||||
|
@ -226,7 +238,7 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
next_poll = None;
|
||||
}
|
||||
}
|
||||
Event::VirtualDeviceFed(protocol) if protocol == PortProtocol::Tcp => {
|
||||
Event::VirtualDeviceFed(PortProtocol::Tcp) => {
|
||||
next_poll = None;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -236,3 +248,10 @@ impl VirtualInterfacePoll for TcpVirtualInterface {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn addr_length(addr: &IpAddress) -> u8 {
|
||||
match addr.version() {
|
||||
IpVersion::Ipv4 => 32,
|
||||
IpVersion::Ipv6 => 128,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
use anyhow::Context;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::events::Event;
|
||||
use crate::{Bus, PortProtocol};
|
||||
use async_trait::async_trait;
|
||||
use smoltcp::iface::{InterfaceBuilder, SocketHandle};
|
||||
use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer};
|
||||
use smoltcp::wire::{IpAddress, IpCidr};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::config::PortForwardConfig;
|
||||
use crate::events::Event;
|
||||
use crate::virtual_device::VirtualIpDevice;
|
||||
use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort};
|
||||
use crate::{Bus, PortProtocol};
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use smoltcp::iface::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;
|
||||
|
||||
|
@ -20,6 +25,7 @@ pub struct UdpVirtualInterface {
|
|||
source_peer_ip: IpAddr,
|
||||
port_forwards: Vec<PortForwardConfig>,
|
||||
bus: Bus,
|
||||
sockets: SocketSet<'static>,
|
||||
}
|
||||
|
||||
impl UdpVirtualInterface {
|
||||
|
@ -33,44 +39,47 @@ impl UdpVirtualInterface {
|
|||
.collect(),
|
||||
source_peer_ip,
|
||||
bus,
|
||||
sockets: SocketSet::new([]),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<UdpSocket<'static>> {
|
||||
static mut UDP_SERVER_RX_META: [UdpPacketMetadata; 0] = [];
|
||||
fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result<udp::Socket<'static>> {
|
||||
static mut UDP_SERVER_RX_META: [udp::PacketMetadata; 0] = [];
|
||||
static mut UDP_SERVER_RX_DATA: [u8; 0] = [];
|
||||
static mut UDP_SERVER_TX_META: [UdpPacketMetadata; 0] = [];
|
||||
static mut UDP_SERVER_TX_META: [udp::PacketMetadata; 0] = [];
|
||||
static mut UDP_SERVER_TX_DATA: [u8; 0] = [];
|
||||
let udp_rx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe {
|
||||
let udp_rx_buffer =
|
||||
udp::PacketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe {
|
||||
&mut UDP_SERVER_RX_DATA[..]
|
||||
});
|
||||
let udp_tx_buffer = UdpSocketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe {
|
||||
let udp_tx_buffer =
|
||||
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
|
||||
.bind((
|
||||
IpAddress::from(port_forward.destination.ip()),
|
||||
port_forward.destination.port(),
|
||||
))
|
||||
.with_context(|| "UDP virtual server socket failed to bind")?;
|
||||
.context("UDP virtual server socket failed to bind")?;
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
fn new_client_socket(
|
||||
source_peer_ip: IpAddr,
|
||||
client_port: VirtualPort,
|
||||
) -> anyhow::Result<UdpSocket<'static>> {
|
||||
let rx_meta = vec![UdpPacketMetadata::EMPTY; 10];
|
||||
let tx_meta = vec![UdpPacketMetadata::EMPTY; 10];
|
||||
) -> anyhow::Result<udp::Socket<'static>> {
|
||||
let rx_meta = vec![udp::PacketMetadata::EMPTY; 10];
|
||||
let tx_meta = vec![udp::PacketMetadata::EMPTY; 10];
|
||||
let rx_data = vec![0u8; MAX_PACKET];
|
||||
let tx_data = vec![0u8; MAX_PACKET];
|
||||
let udp_rx_buffer = UdpSocketBuffer::new(rx_meta, rx_data);
|
||||
let udp_tx_buffer = UdpSocketBuffer::new(tx_meta, tx_data);
|
||||
let mut socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer);
|
||||
let udp_rx_buffer = udp::PacketBuffer::new(rx_meta, rx_data);
|
||||
let udp_tx_buffer = udp::PacketBuffer::new(tx_meta, tx_data);
|
||||
let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
|
||||
socket
|
||||
.bind((IpAddress::from(source_peer_ip), client_port.num()))
|
||||
.with_context(|| "UDP virtual client failed to bind")?;
|
||||
.context("UDP virtual client failed to bind")?;
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
|
@ -82,26 +91,32 @@ impl UdpVirtualInterface {
|
|||
}
|
||||
addresses
|
||||
.into_iter()
|
||||
.map(|addr| IpCidr::new(addr, 32))
|
||||
.map(|addr| IpCidr::new(addr, addr_length(&addr)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl VirtualInterfacePoll for UdpVirtualInterface {
|
||||
async fn poll_loop(self, 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
|
||||
let addresses = self.addresses();
|
||||
let config = Config::new(HardwareAddress::Ip);
|
||||
|
||||
// Create virtual interface (contains smoltcp state machine)
|
||||
let mut iface = InterfaceBuilder::new(device, vec![])
|
||||
.ip_addrs(addresses)
|
||||
.finalize();
|
||||
let mut iface = Interface::new(config, &mut device, Instant::now());
|
||||
iface.update_ip_addrs(|ip_addrs| {
|
||||
addresses.into_iter().for_each(|addr| {
|
||||
ip_addrs
|
||||
.push(addr)
|
||||
.expect("maximum number of IPs in UDP interface reached");
|
||||
});
|
||||
});
|
||||
|
||||
// Create virtual server for each port forward
|
||||
for port_forward in self.port_forwards.iter() {
|
||||
let server_socket = UdpVirtualInterface::new_server_socket(*port_forward)?;
|
||||
iface.add_socket(server_socket);
|
||||
self.sockets.add(server_socket);
|
||||
}
|
||||
|
||||
// The next time to poll the interface. Can be None for instant poll.
|
||||
|
@ -114,7 +129,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
let mut port_client_handle_map: HashMap<VirtualPort, SocketHandle> = HashMap::new();
|
||||
|
||||
// Data packets to send from a virtual client
|
||||
let mut send_queue: HashMap<VirtualPort, VecDeque<(PortForwardConfig, Vec<u8>)>> =
|
||||
let mut send_queue: HashMap<VirtualPort, VecDeque<(PortForwardConfig, Bytes)>> =
|
||||
HashMap::new();
|
||||
|
||||
loop {
|
||||
|
@ -126,16 +141,12 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
} => {
|
||||
let loop_start = smoltcp::time::Instant::now();
|
||||
|
||||
match iface.poll(loop_start) {
|
||||
Ok(processed) if processed => {
|
||||
trace!("UDP virtual interface polled some packets to be processed");
|
||||
}
|
||||
Err(e) => error!("UDP virtual interface poll error: {:?}", e),
|
||||
_ => {}
|
||||
if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged {
|
||||
log::trace!("UDP virtual interface polled some packets to be processed");
|
||||
}
|
||||
|
||||
for (virtual_port, client_handle) in port_client_handle_map.iter() {
|
||||
let client_socket = iface.get_socket::<UdpSocket>(*client_handle);
|
||||
let client_socket = self.sockets.get_mut::<udp::Socket>(*client_handle);
|
||||
if client_socket.can_send() {
|
||||
if let Some(send_queue) = send_queue.get_mut(virtual_port) {
|
||||
let to_transfer = send_queue.pop_front();
|
||||
|
@ -143,7 +154,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
client_socket
|
||||
.send_slice(
|
||||
&data,
|
||||
(IpAddress::from(port_forward.destination.ip()), port_forward.destination.port()).into(),
|
||||
UdpMetadata::from(port_forward.destination),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
error!(
|
||||
|
@ -158,7 +169,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
match client_socket.recv() {
|
||||
Ok((data, _peer)) => {
|
||||
if !data.is_empty() {
|
||||
endpoint.send(Event::RemoteData(*virtual_port, data.to_vec()));
|
||||
endpoint.send(Event::RemoteData(*virtual_port, data.to_vec().into()));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -171,7 +182,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
}
|
||||
|
||||
// The virtual interface determines the next time to poll (this is to reduce unnecessary polls)
|
||||
next_poll = match iface.poll_delay(loop_start) {
|
||||
next_poll = match iface.poll_delay(loop_start, &self.sockets) {
|
||||
Some(smoltcp::time::Duration::ZERO) => None,
|
||||
Some(delay) => {
|
||||
trace!("UDP Virtual interface delayed next poll by {}", delay);
|
||||
|
@ -189,7 +200,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
} else {
|
||||
// Client socket does not exist
|
||||
let client_socket = UdpVirtualInterface::new_client_socket(self.source_peer_ip, virtual_port)?;
|
||||
let client_handle = iface.add_socket(client_socket);
|
||||
let client_handle = self.sockets.add(client_socket);
|
||||
|
||||
// Add handle to map
|
||||
port_client_handle_map.insert(virtual_port, client_handle);
|
||||
|
@ -197,7 +208,7 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
}
|
||||
next_poll = None;
|
||||
}
|
||||
Event::VirtualDeviceFed(protocol) if protocol == PortProtocol::Udp => {
|
||||
Event::VirtualDeviceFed(PortProtocol::Udp) => {
|
||||
next_poll = None;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -207,3 +218,10 @@ impl VirtualInterfacePoll for UdpVirtualInterface {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn addr_length(addr: &IpAddress) -> u8 {
|
||||
match addr.version() {
|
||||
IpVersion::Ipv4 => 32,
|
||||
IpVersion::Ipv6 => 128,
|
||||
}
|
||||
}
|
||||
|
|
73
src/wg.rs
73
src/wg.rs
|
@ -1,12 +1,15 @@
|
|||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::Bus;
|
||||
use anyhow::Context;
|
||||
use async_recursion::async_recursion;
|
||||
use boringtun::noise::errors::WireGuardError;
|
||||
use boringtun::noise::{Tunn, TunnResult};
|
||||
use log::Level;
|
||||
use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet};
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::config::{Config, PortProtocol};
|
||||
use crate::events::Event;
|
||||
|
@ -21,7 +24,7 @@ const MAX_PACKET: usize = 65536;
|
|||
pub struct WireGuardTunnel {
|
||||
pub(crate) source_peer_ip: IpAddr,
|
||||
/// `boringtun` peer/tunnel implementation, used for crypto & WG protocol.
|
||||
peer: Box<Tunn>,
|
||||
peer: Mutex<Box<Tunn>>,
|
||||
/// The UDP socket for the public WireGuard endpoint to connect to.
|
||||
udp: UdpSocket,
|
||||
/// The address of the public WireGuard endpoint (UDP).
|
||||
|
@ -34,11 +37,11 @@ impl WireGuardTunnel {
|
|||
/// Initialize a new WireGuard tunnel.
|
||||
pub async fn new(config: &Config, bus: Bus) -> anyhow::Result<Self> {
|
||||
let source_peer_ip = config.source_peer_ip;
|
||||
let peer = Self::create_tunnel(config)?;
|
||||
let peer = Mutex::new(Box::new(Self::create_tunnel(config)?));
|
||||
let endpoint = config.endpoint_addr;
|
||||
let udp = UdpSocket::bind(config.endpoint_bind_addr)
|
||||
.await
|
||||
.with_context(|| "Failed to create UDP socket for WireGuard connection")?;
|
||||
.context("Failed to create UDP socket for WireGuard connection")?;
|
||||
|
||||
Ok(Self {
|
||||
source_peer_ip,
|
||||
|
@ -53,12 +56,16 @@ impl WireGuardTunnel {
|
|||
pub async fn send_ip_packet(&self, packet: &[u8]) -> anyhow::Result<()> {
|
||||
trace_ip_packet("Sending IP packet", packet);
|
||||
let mut send_buf = [0u8; MAX_PACKET];
|
||||
match self.peer.encapsulate(packet, &mut send_buf) {
|
||||
let encapsulate_result = {
|
||||
let mut peer = self.peer.lock().await;
|
||||
peer.encapsulate(packet, &mut send_buf)
|
||||
};
|
||||
match encapsulate_result {
|
||||
TunnResult::WriteToNetwork(packet) => {
|
||||
self.udp
|
||||
.send_to(packet, self.endpoint)
|
||||
.await
|
||||
.with_context(|| "Failed to send encrypted IP packet to WireGuard endpoint.")?;
|
||||
.context("Failed to send encrypted IP packet to WireGuard endpoint.")?;
|
||||
debug!(
|
||||
"Sent {} bytes to WireGuard endpoint (encrypted IP packet)",
|
||||
packet.len()
|
||||
|
@ -102,7 +109,14 @@ impl WireGuardTunnel {
|
|||
|
||||
loop {
|
||||
let mut send_buf = [0u8; MAX_PACKET];
|
||||
match 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;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) -> () {
|
||||
match result {
|
||||
TunnResult::WriteToNetwork(packet) => {
|
||||
debug!(
|
||||
"Sending routine packet of {} bytes to WireGuard endpoint",
|
||||
|
@ -118,6 +132,18 @@ impl WireGuardTunnel {
|
|||
}
|
||||
};
|
||||
}
|
||||
TunnResult::Err(WireGuardError::ConnectionExpired) => {
|
||||
warn!("Wireguard handshake has expired!");
|
||||
|
||||
let mut buf = vec![0u8; MAX_PACKET];
|
||||
let result = self
|
||||
.peer
|
||||
.lock()
|
||||
.await
|
||||
.format_handshake_initiation(&mut buf[..], false);
|
||||
|
||||
self.handle_routine_tun_result(result).await
|
||||
}
|
||||
TunnResult::Err(e) => {
|
||||
error!(
|
||||
"Failed to prepare routine packet for WireGuard endpoint: {:?}",
|
||||
|
@ -131,8 +157,7 @@ impl WireGuardTunnel {
|
|||
other => {
|
||||
warn!("Unexpected WireGuard routine task state: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// WireGuard consumption task. Receives encrypted packets from the WireGuard endpoint,
|
||||
|
@ -156,7 +181,11 @@ impl WireGuardTunnel {
|
|||
};
|
||||
|
||||
let data = &recv_buf[..size];
|
||||
match self.peer.decapsulate(None, data, &mut send_buf) {
|
||||
let decapsulate_result = {
|
||||
let mut peer = self.peer.lock().await;
|
||||
peer.decapsulate(None, data, &mut send_buf)
|
||||
};
|
||||
match decapsulate_result {
|
||||
TunnResult::WriteToNetwork(packet) => {
|
||||
match self.udp.send_to(packet, self.endpoint).await {
|
||||
Ok(_) => {}
|
||||
|
@ -165,9 +194,10 @@ impl WireGuardTunnel {
|
|||
continue;
|
||||
}
|
||||
};
|
||||
let mut peer = self.peer.lock().await;
|
||||
loop {
|
||||
let mut send_buf = [0u8; MAX_PACKET];
|
||||
match self.peer.decapsulate(None, &[], &mut send_buf) {
|
||||
match peer.decapsulate(None, &[], &mut send_buf) {
|
||||
TunnResult::WriteToNetwork(packet) => {
|
||||
match self.udp.send_to(packet, self.endpoint).await {
|
||||
Ok(_) => {}
|
||||
|
@ -193,7 +223,7 @@ impl WireGuardTunnel {
|
|||
trace_ip_packet("Received IP packet", packet);
|
||||
|
||||
if let Some(proto) = self.route_protocol(packet) {
|
||||
endpoint.send(Event::InboundInternetPacket(proto, packet.into()));
|
||||
endpoint.send(Event::InboundInternetPacket(proto, packet.to_vec().into()));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -201,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(
|
||||
config.private_key.clone(),
|
||||
config.endpoint_public_key.clone(),
|
||||
None,
|
||||
private,
|
||||
public,
|
||||
config.preshared_key,
|
||||
config.keepalive_seconds,
|
||||
0,
|
||||
None,
|
||||
)
|
||||
.map_err(|s| anyhow::anyhow!("{}", s))
|
||||
.with_context(|| "Failed to initialize boringtun Tunn")
|
||||
.context("Failed to initialize boringtun Tunn")
|
||||
}
|
||||
|
||||
/// Determine the inner protocol of the incoming IP packet (TCP/UDP).
|
||||
|
@ -220,8 +253,8 @@ impl WireGuardTunnel {
|
|||
Ok(IpVersion::Ipv4) => Ipv4Packet::new_checked(&packet)
|
||||
.ok()
|
||||
// Only care if the packet is destined for this tunnel
|
||||
.filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip)
|
||||
.and_then(|packet| match packet.protocol() {
|
||||
.filter(|packet| packet.dst_addr() == self.source_peer_ip)
|
||||
.and_then(|packet| match packet.next_header() {
|
||||
IpProtocol::Tcp => Some(PortProtocol::Tcp),
|
||||
IpProtocol::Udp => Some(PortProtocol::Udp),
|
||||
// Unrecognized protocol, so we cannot determine where to route
|
||||
|
@ -230,7 +263,7 @@ impl WireGuardTunnel {
|
|||
Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet)
|
||||
.ok()
|
||||
// Only care if the packet is destined for this tunnel
|
||||
.filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip)
|
||||
.filter(|packet| packet.dst_addr() == self.source_peer_ip)
|
||||
.and_then(|packet| match packet.next_header() {
|
||||
IpProtocol::Tcp => Some(PortProtocol::Tcp),
|
||||
IpProtocol::Udp => Some(PortProtocol::Udp),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue