├── .github
├── dependabot.yml
└── workflows
│ ├── docker.yml
│ ├── release.yml
│ └── rust.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── docker
├── .dockerignore
├── Dockerfile
├── README.md
├── docker-compose.yml
└── phantun.sh
├── fake-tcp
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
└── src
│ ├── lib.rs
│ └── packet.rs
├── images
├── packet-headers.png
├── phantun-vs-udp2raw-benchmark-result.png
└── traffic-flow.png
└── phantun
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
└── src
├── bin
├── client.rs
└── server.rs
├── lib.rs
└── utils.rs
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 |
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 |
9 | - package-ecosystem: "cargo"
10 | directory: "/"
11 | schedule:
12 | interval: "daily"
13 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: Docker image build
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-22.04
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup QEMU
17 | uses: docker/setup-qemu-action@v3
18 | with:
19 | platforms: linux/amd64
20 |
21 | - name: Setup Docker Buildx
22 | uses: docker/setup-buildx-action@v3
23 |
24 | - name: Build Docker Image
25 | uses: docker/build-push-action@v6
26 | with:
27 | context: .
28 | file: docker/Dockerfile
29 | tags: phantun
30 | platforms: linux/amd64
31 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build Releases
2 | on:
3 | push:
4 | tags:
5 | - v*.*.*
6 |
7 | env:
8 | CARGO_TERM_COLOR: always
9 |
10 | jobs:
11 | build-cross:
12 | runs-on: ubuntu-latest
13 | env:
14 | RUST_BACKTRACE: full
15 | strategy:
16 | matrix:
17 | target:
18 | - x86_64-unknown-linux-gnu
19 | - x86_64-unknown-linux-musl
20 | - i686-unknown-linux-gnu
21 | - i686-unknown-linux-musl
22 | - armv7-unknown-linux-gnueabihf
23 | - armv7-unknown-linux-musleabihf
24 | - arm-unknown-linux-gnueabihf
25 | - arm-unknown-linux-musleabihf
26 | - aarch64-unknown-linux-gnu
27 | - aarch64-unknown-linux-musl
28 |
29 | steps:
30 | - uses: actions/checkout@v4
31 | - uses: actions-rs/toolchain@v1
32 | with:
33 | toolchain: stable
34 | target: ${{ matrix.target }}
35 | override: true
36 | - uses: actions-rs/cargo@v1
37 | with:
38 | use-cross: true
39 | command: build
40 | args: --release --target ${{ matrix.target }}
41 | - name: Rename artifacts and compress
42 | run: |
43 | cd target/${{ matrix.target }}/release
44 | mv client phantun_client
45 | mv server phantun_server
46 | zip phantun_${{ matrix.target }}.zip phantun_client phantun_server
47 |
48 | - name: Upload Github Assets
49 | uses: softprops/action-gh-release@v2
50 | with:
51 | files: target/${{ matrix.target }}/release/*.zip
52 | prerelease: ${{ contains(github.ref, '-') }}
53 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on: [push, pull_request]
4 |
5 | env:
6 | CARGO_TERM_COLOR: always
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions-rs/toolchain@v1
16 | with:
17 | toolchain: stable
18 | - name: Run lint
19 | run: cargo clippy --verbose
20 | - name: Build
21 | run: cargo build --verbose
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.24.2"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler2"
16 | version = "2.0.0"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19 |
20 | [[package]]
21 | name = "aho-corasick"
22 | version = "1.1.3"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
25 | dependencies = [
26 | "memchr",
27 | ]
28 |
29 | [[package]]
30 | name = "anstream"
31 | version = "0.6.18"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
34 | dependencies = [
35 | "anstyle",
36 | "anstyle-parse",
37 | "anstyle-query",
38 | "anstyle-wincon",
39 | "colorchoice",
40 | "is_terminal_polyfill",
41 | "utf8parse",
42 | ]
43 |
44 | [[package]]
45 | name = "anstyle"
46 | version = "1.0.10"
47 | source = "registry+https://github.com/rust-lang/crates.io-index"
48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
49 |
50 | [[package]]
51 | name = "anstyle-parse"
52 | version = "0.2.6"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
55 | dependencies = [
56 | "utf8parse",
57 | ]
58 |
59 | [[package]]
60 | name = "anstyle-query"
61 | version = "1.1.2"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
64 | dependencies = [
65 | "windows-sys 0.59.0",
66 | ]
67 |
68 | [[package]]
69 | name = "anstyle-wincon"
70 | version = "3.0.7"
71 | source = "registry+https://github.com/rust-lang/crates.io-index"
72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
73 | dependencies = [
74 | "anstyle",
75 | "once_cell",
76 | "windows-sys 0.59.0",
77 | ]
78 |
79 | [[package]]
80 | name = "autocfg"
81 | version = "1.4.0"
82 | source = "registry+https://github.com/rust-lang/crates.io-index"
83 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
84 |
85 | [[package]]
86 | name = "backtrace"
87 | version = "0.3.74"
88 | source = "registry+https://github.com/rust-lang/crates.io-index"
89 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
90 | dependencies = [
91 | "addr2line",
92 | "cfg-if",
93 | "libc",
94 | "miniz_oxide",
95 | "object",
96 | "rustc-demangle",
97 | "windows-targets",
98 | ]
99 |
100 | [[package]]
101 | name = "bitflags"
102 | version = "2.9.0"
103 | source = "registry+https://github.com/rust-lang/crates.io-index"
104 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
105 |
106 | [[package]]
107 | name = "bumpalo"
108 | version = "3.17.0"
109 | source = "registry+https://github.com/rust-lang/crates.io-index"
110 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
111 |
112 | [[package]]
113 | name = "byteorder"
114 | version = "1.5.0"
115 | source = "registry+https://github.com/rust-lang/crates.io-index"
116 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
117 |
118 | [[package]]
119 | name = "bytes"
120 | version = "1.10.1"
121 | source = "registry+https://github.com/rust-lang/crates.io-index"
122 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
123 |
124 | [[package]]
125 | name = "cfg-if"
126 | version = "1.0.0"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
129 |
130 | [[package]]
131 | name = "cfg_aliases"
132 | version = "0.2.1"
133 | source = "registry+https://github.com/rust-lang/crates.io-index"
134 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
135 |
136 | [[package]]
137 | name = "clap"
138 | version = "4.5.32"
139 | source = "registry+https://github.com/rust-lang/crates.io-index"
140 | checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
141 | dependencies = [
142 | "clap_builder",
143 | ]
144 |
145 | [[package]]
146 | name = "clap_builder"
147 | version = "4.5.32"
148 | source = "registry+https://github.com/rust-lang/crates.io-index"
149 | checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
150 | dependencies = [
151 | "anstream",
152 | "anstyle",
153 | "clap_lex",
154 | "strsim",
155 | ]
156 |
157 | [[package]]
158 | name = "clap_lex"
159 | version = "0.7.4"
160 | source = "registry+https://github.com/rust-lang/crates.io-index"
161 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
162 |
163 | [[package]]
164 | name = "colorchoice"
165 | version = "1.0.3"
166 | source = "registry+https://github.com/rust-lang/crates.io-index"
167 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
168 |
169 | [[package]]
170 | name = "either"
171 | version = "1.15.0"
172 | source = "registry+https://github.com/rust-lang/crates.io-index"
173 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
174 |
175 | [[package]]
176 | name = "env_logger"
177 | version = "0.10.2"
178 | source = "registry+https://github.com/rust-lang/crates.io-index"
179 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
180 | dependencies = [
181 | "humantime",
182 | "is-terminal",
183 | "log",
184 | "regex",
185 | "termcolor",
186 | ]
187 |
188 | [[package]]
189 | name = "fake-tcp"
190 | version = "0.6.0"
191 | dependencies = [
192 | "bytes",
193 | "flume",
194 | "internet-checksum",
195 | "log",
196 | "pnet",
197 | "rand",
198 | "tokio",
199 | "tokio-tun",
200 | ]
201 |
202 | [[package]]
203 | name = "flume"
204 | version = "0.11.1"
205 | source = "registry+https://github.com/rust-lang/crates.io-index"
206 | checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
207 | dependencies = [
208 | "futures-core",
209 | "futures-sink",
210 | "nanorand",
211 | "spin",
212 | ]
213 |
214 | [[package]]
215 | name = "futures-core"
216 | version = "0.3.31"
217 | source = "registry+https://github.com/rust-lang/crates.io-index"
218 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
219 |
220 | [[package]]
221 | name = "futures-sink"
222 | version = "0.3.31"
223 | source = "registry+https://github.com/rust-lang/crates.io-index"
224 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
225 |
226 | [[package]]
227 | name = "getrandom"
228 | version = "0.2.15"
229 | source = "registry+https://github.com/rust-lang/crates.io-index"
230 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
231 | dependencies = [
232 | "cfg-if",
233 | "js-sys",
234 | "libc",
235 | "wasi 0.11.0+wasi-snapshot-preview1",
236 | "wasm-bindgen",
237 | ]
238 |
239 | [[package]]
240 | name = "getrandom"
241 | version = "0.3.1"
242 | source = "registry+https://github.com/rust-lang/crates.io-index"
243 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
244 | dependencies = [
245 | "cfg-if",
246 | "libc",
247 | "wasi 0.13.3+wasi-0.2.2",
248 | "windows-targets",
249 | ]
250 |
251 | [[package]]
252 | name = "gimli"
253 | version = "0.31.1"
254 | source = "registry+https://github.com/rust-lang/crates.io-index"
255 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
256 |
257 | [[package]]
258 | name = "glob"
259 | version = "0.3.2"
260 | source = "registry+https://github.com/rust-lang/crates.io-index"
261 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
262 |
263 | [[package]]
264 | name = "hermit-abi"
265 | version = "0.3.9"
266 | source = "registry+https://github.com/rust-lang/crates.io-index"
267 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
268 |
269 | [[package]]
270 | name = "hermit-abi"
271 | version = "0.5.0"
272 | source = "registry+https://github.com/rust-lang/crates.io-index"
273 | checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
274 |
275 | [[package]]
276 | name = "humantime"
277 | version = "2.2.0"
278 | source = "registry+https://github.com/rust-lang/crates.io-index"
279 | checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
280 |
281 | [[package]]
282 | name = "internet-checksum"
283 | version = "0.2.1"
284 | source = "registry+https://github.com/rust-lang/crates.io-index"
285 | checksum = "fc6d6206008e25125b1f97fbe5d309eb7b85141cf9199d52dbd3729a1584dd16"
286 |
287 | [[package]]
288 | name = "ipnetwork"
289 | version = "0.20.0"
290 | source = "registry+https://github.com/rust-lang/crates.io-index"
291 | checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
292 | dependencies = [
293 | "serde",
294 | ]
295 |
296 | [[package]]
297 | name = "is-terminal"
298 | version = "0.4.16"
299 | source = "registry+https://github.com/rust-lang/crates.io-index"
300 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
301 | dependencies = [
302 | "hermit-abi 0.5.0",
303 | "libc",
304 | "windows-sys 0.59.0",
305 | ]
306 |
307 | [[package]]
308 | name = "is_terminal_polyfill"
309 | version = "1.70.1"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
312 |
313 | [[package]]
314 | name = "js-sys"
315 | version = "0.3.77"
316 | source = "registry+https://github.com/rust-lang/crates.io-index"
317 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
318 | dependencies = [
319 | "once_cell",
320 | "wasm-bindgen",
321 | ]
322 |
323 | [[package]]
324 | name = "libc"
325 | version = "0.2.171"
326 | source = "registry+https://github.com/rust-lang/crates.io-index"
327 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
328 |
329 | [[package]]
330 | name = "lock_api"
331 | version = "0.4.12"
332 | source = "registry+https://github.com/rust-lang/crates.io-index"
333 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
334 | dependencies = [
335 | "autocfg",
336 | "scopeguard",
337 | ]
338 |
339 | [[package]]
340 | name = "log"
341 | version = "0.4.26"
342 | source = "registry+https://github.com/rust-lang/crates.io-index"
343 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
344 |
345 | [[package]]
346 | name = "memchr"
347 | version = "2.7.4"
348 | source = "registry+https://github.com/rust-lang/crates.io-index"
349 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
350 |
351 | [[package]]
352 | name = "memoffset"
353 | version = "0.9.1"
354 | source = "registry+https://github.com/rust-lang/crates.io-index"
355 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
356 | dependencies = [
357 | "autocfg",
358 | ]
359 |
360 | [[package]]
361 | name = "miniz_oxide"
362 | version = "0.8.5"
363 | source = "registry+https://github.com/rust-lang/crates.io-index"
364 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
365 | dependencies = [
366 | "adler2",
367 | ]
368 |
369 | [[package]]
370 | name = "mio"
371 | version = "1.0.3"
372 | source = "registry+https://github.com/rust-lang/crates.io-index"
373 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
374 | dependencies = [
375 | "libc",
376 | "wasi 0.11.0+wasi-snapshot-preview1",
377 | "windows-sys 0.52.0",
378 | ]
379 |
380 | [[package]]
381 | name = "nanorand"
382 | version = "0.7.0"
383 | source = "registry+https://github.com/rust-lang/crates.io-index"
384 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
385 | dependencies = [
386 | "getrandom 0.2.15",
387 | ]
388 |
389 | [[package]]
390 | name = "neli"
391 | version = "0.6.5"
392 | source = "registry+https://github.com/rust-lang/crates.io-index"
393 | checksum = "93062a0dce6da2517ea35f301dfc88184ce18d3601ec786a727a87bf535deca9"
394 | dependencies = [
395 | "byteorder",
396 | "libc",
397 | "log",
398 | "neli-proc-macros",
399 | ]
400 |
401 | [[package]]
402 | name = "neli-proc-macros"
403 | version = "0.1.4"
404 | source = "registry+https://github.com/rust-lang/crates.io-index"
405 | checksum = "0c8034b7fbb6f9455b2a96c19e6edf8dc9fc34c70449938d8ee3b4df363f61fe"
406 | dependencies = [
407 | "either",
408 | "proc-macro2",
409 | "quote",
410 | "serde",
411 | "syn 1.0.109",
412 | ]
413 |
414 | [[package]]
415 | name = "nix"
416 | version = "0.29.0"
417 | source = "registry+https://github.com/rust-lang/crates.io-index"
418 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
419 | dependencies = [
420 | "bitflags",
421 | "cfg-if",
422 | "cfg_aliases",
423 | "libc",
424 | "memoffset",
425 | ]
426 |
427 | [[package]]
428 | name = "no-std-net"
429 | version = "0.6.0"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
432 |
433 | [[package]]
434 | name = "num_cpus"
435 | version = "1.16.0"
436 | source = "registry+https://github.com/rust-lang/crates.io-index"
437 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
438 | dependencies = [
439 | "hermit-abi 0.3.9",
440 | "libc",
441 | ]
442 |
443 | [[package]]
444 | name = "object"
445 | version = "0.36.7"
446 | source = "registry+https://github.com/rust-lang/crates.io-index"
447 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
448 | dependencies = [
449 | "memchr",
450 | ]
451 |
452 | [[package]]
453 | name = "once_cell"
454 | version = "1.21.1"
455 | source = "registry+https://github.com/rust-lang/crates.io-index"
456 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
457 |
458 | [[package]]
459 | name = "parking_lot"
460 | version = "0.12.3"
461 | source = "registry+https://github.com/rust-lang/crates.io-index"
462 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
463 | dependencies = [
464 | "lock_api",
465 | "parking_lot_core",
466 | ]
467 |
468 | [[package]]
469 | name = "parking_lot_core"
470 | version = "0.9.10"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
473 | dependencies = [
474 | "cfg-if",
475 | "libc",
476 | "redox_syscall",
477 | "smallvec",
478 | "windows-targets",
479 | ]
480 |
481 | [[package]]
482 | name = "phantun"
483 | version = "0.7.0"
484 | dependencies = [
485 | "clap",
486 | "fake-tcp",
487 | "log",
488 | "neli",
489 | "nix",
490 | "num_cpus",
491 | "pretty_env_logger",
492 | "socket2",
493 | "tokio",
494 | "tokio-tun",
495 | "tokio-util",
496 | ]
497 |
498 | [[package]]
499 | name = "pin-project-lite"
500 | version = "0.2.16"
501 | source = "registry+https://github.com/rust-lang/crates.io-index"
502 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
503 |
504 | [[package]]
505 | name = "pnet"
506 | version = "0.35.0"
507 | source = "registry+https://github.com/rust-lang/crates.io-index"
508 | checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d"
509 | dependencies = [
510 | "ipnetwork",
511 | "pnet_base",
512 | "pnet_datalink",
513 | "pnet_packet",
514 | "pnet_sys",
515 | "pnet_transport",
516 | ]
517 |
518 | [[package]]
519 | name = "pnet_base"
520 | version = "0.35.0"
521 | source = "registry+https://github.com/rust-lang/crates.io-index"
522 | checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
523 | dependencies = [
524 | "no-std-net",
525 | ]
526 |
527 | [[package]]
528 | name = "pnet_datalink"
529 | version = "0.35.0"
530 | source = "registry+https://github.com/rust-lang/crates.io-index"
531 | checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7"
532 | dependencies = [
533 | "ipnetwork",
534 | "libc",
535 | "pnet_base",
536 | "pnet_sys",
537 | "winapi",
538 | ]
539 |
540 | [[package]]
541 | name = "pnet_macros"
542 | version = "0.35.0"
543 | source = "registry+https://github.com/rust-lang/crates.io-index"
544 | checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863"
545 | dependencies = [
546 | "proc-macro2",
547 | "quote",
548 | "regex",
549 | "syn 2.0.100",
550 | ]
551 |
552 | [[package]]
553 | name = "pnet_macros_support"
554 | version = "0.35.0"
555 | source = "registry+https://github.com/rust-lang/crates.io-index"
556 | checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab"
557 | dependencies = [
558 | "pnet_base",
559 | ]
560 |
561 | [[package]]
562 | name = "pnet_packet"
563 | version = "0.35.0"
564 | source = "registry+https://github.com/rust-lang/crates.io-index"
565 | checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f"
566 | dependencies = [
567 | "glob",
568 | "pnet_base",
569 | "pnet_macros",
570 | "pnet_macros_support",
571 | ]
572 |
573 | [[package]]
574 | name = "pnet_sys"
575 | version = "0.35.0"
576 | source = "registry+https://github.com/rust-lang/crates.io-index"
577 | checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b"
578 | dependencies = [
579 | "libc",
580 | "winapi",
581 | ]
582 |
583 | [[package]]
584 | name = "pnet_transport"
585 | version = "0.35.0"
586 | source = "registry+https://github.com/rust-lang/crates.io-index"
587 | checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf"
588 | dependencies = [
589 | "libc",
590 | "pnet_base",
591 | "pnet_packet",
592 | "pnet_sys",
593 | ]
594 |
595 | [[package]]
596 | name = "ppv-lite86"
597 | version = "0.2.21"
598 | source = "registry+https://github.com/rust-lang/crates.io-index"
599 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
600 | dependencies = [
601 | "zerocopy",
602 | ]
603 |
604 | [[package]]
605 | name = "pretty_env_logger"
606 | version = "0.5.0"
607 | source = "registry+https://github.com/rust-lang/crates.io-index"
608 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c"
609 | dependencies = [
610 | "env_logger",
611 | "log",
612 | ]
613 |
614 | [[package]]
615 | name = "proc-macro2"
616 | version = "1.0.94"
617 | source = "registry+https://github.com/rust-lang/crates.io-index"
618 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
619 | dependencies = [
620 | "unicode-ident",
621 | ]
622 |
623 | [[package]]
624 | name = "quote"
625 | version = "1.0.40"
626 | source = "registry+https://github.com/rust-lang/crates.io-index"
627 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
628 | dependencies = [
629 | "proc-macro2",
630 | ]
631 |
632 | [[package]]
633 | name = "rand"
634 | version = "0.9.0"
635 | source = "registry+https://github.com/rust-lang/crates.io-index"
636 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
637 | dependencies = [
638 | "rand_chacha",
639 | "rand_core",
640 | "zerocopy",
641 | ]
642 |
643 | [[package]]
644 | name = "rand_chacha"
645 | version = "0.9.0"
646 | source = "registry+https://github.com/rust-lang/crates.io-index"
647 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
648 | dependencies = [
649 | "ppv-lite86",
650 | "rand_core",
651 | ]
652 |
653 | [[package]]
654 | name = "rand_core"
655 | version = "0.9.3"
656 | source = "registry+https://github.com/rust-lang/crates.io-index"
657 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
658 | dependencies = [
659 | "getrandom 0.3.1",
660 | ]
661 |
662 | [[package]]
663 | name = "redox_syscall"
664 | version = "0.5.10"
665 | source = "registry+https://github.com/rust-lang/crates.io-index"
666 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
667 | dependencies = [
668 | "bitflags",
669 | ]
670 |
671 | [[package]]
672 | name = "regex"
673 | version = "1.11.1"
674 | source = "registry+https://github.com/rust-lang/crates.io-index"
675 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
676 | dependencies = [
677 | "aho-corasick",
678 | "memchr",
679 | "regex-automata",
680 | "regex-syntax",
681 | ]
682 |
683 | [[package]]
684 | name = "regex-automata"
685 | version = "0.4.9"
686 | source = "registry+https://github.com/rust-lang/crates.io-index"
687 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
688 | dependencies = [
689 | "aho-corasick",
690 | "memchr",
691 | "regex-syntax",
692 | ]
693 |
694 | [[package]]
695 | name = "regex-syntax"
696 | version = "0.8.5"
697 | source = "registry+https://github.com/rust-lang/crates.io-index"
698 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
699 |
700 | [[package]]
701 | name = "rustc-demangle"
702 | version = "0.1.24"
703 | source = "registry+https://github.com/rust-lang/crates.io-index"
704 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
705 |
706 | [[package]]
707 | name = "scopeguard"
708 | version = "1.2.0"
709 | source = "registry+https://github.com/rust-lang/crates.io-index"
710 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
711 |
712 | [[package]]
713 | name = "serde"
714 | version = "1.0.219"
715 | source = "registry+https://github.com/rust-lang/crates.io-index"
716 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
717 | dependencies = [
718 | "serde_derive",
719 | ]
720 |
721 | [[package]]
722 | name = "serde_derive"
723 | version = "1.0.219"
724 | source = "registry+https://github.com/rust-lang/crates.io-index"
725 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
726 | dependencies = [
727 | "proc-macro2",
728 | "quote",
729 | "syn 2.0.100",
730 | ]
731 |
732 | [[package]]
733 | name = "signal-hook-registry"
734 | version = "1.4.2"
735 | source = "registry+https://github.com/rust-lang/crates.io-index"
736 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
737 | dependencies = [
738 | "libc",
739 | ]
740 |
741 | [[package]]
742 | name = "smallvec"
743 | version = "1.14.0"
744 | source = "registry+https://github.com/rust-lang/crates.io-index"
745 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
746 |
747 | [[package]]
748 | name = "socket2"
749 | version = "0.5.8"
750 | source = "registry+https://github.com/rust-lang/crates.io-index"
751 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
752 | dependencies = [
753 | "libc",
754 | "windows-sys 0.52.0",
755 | ]
756 |
757 | [[package]]
758 | name = "spin"
759 | version = "0.9.8"
760 | source = "registry+https://github.com/rust-lang/crates.io-index"
761 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
762 | dependencies = [
763 | "lock_api",
764 | ]
765 |
766 | [[package]]
767 | name = "strsim"
768 | version = "0.11.1"
769 | source = "registry+https://github.com/rust-lang/crates.io-index"
770 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
771 |
772 | [[package]]
773 | name = "syn"
774 | version = "1.0.109"
775 | source = "registry+https://github.com/rust-lang/crates.io-index"
776 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
777 | dependencies = [
778 | "proc-macro2",
779 | "quote",
780 | "unicode-ident",
781 | ]
782 |
783 | [[package]]
784 | name = "syn"
785 | version = "2.0.100"
786 | source = "registry+https://github.com/rust-lang/crates.io-index"
787 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
788 | dependencies = [
789 | "proc-macro2",
790 | "quote",
791 | "unicode-ident",
792 | ]
793 |
794 | [[package]]
795 | name = "termcolor"
796 | version = "1.4.1"
797 | source = "registry+https://github.com/rust-lang/crates.io-index"
798 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
799 | dependencies = [
800 | "winapi-util",
801 | ]
802 |
803 | [[package]]
804 | name = "thiserror"
805 | version = "2.0.12"
806 | source = "registry+https://github.com/rust-lang/crates.io-index"
807 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
808 | dependencies = [
809 | "thiserror-impl",
810 | ]
811 |
812 | [[package]]
813 | name = "thiserror-impl"
814 | version = "2.0.12"
815 | source = "registry+https://github.com/rust-lang/crates.io-index"
816 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
817 | dependencies = [
818 | "proc-macro2",
819 | "quote",
820 | "syn 2.0.100",
821 | ]
822 |
823 | [[package]]
824 | name = "tokio"
825 | version = "1.44.1"
826 | source = "registry+https://github.com/rust-lang/crates.io-index"
827 | checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
828 | dependencies = [
829 | "backtrace",
830 | "bytes",
831 | "libc",
832 | "mio",
833 | "parking_lot",
834 | "pin-project-lite",
835 | "signal-hook-registry",
836 | "socket2",
837 | "tokio-macros",
838 | "windows-sys 0.52.0",
839 | ]
840 |
841 | [[package]]
842 | name = "tokio-macros"
843 | version = "2.5.0"
844 | source = "registry+https://github.com/rust-lang/crates.io-index"
845 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
846 | dependencies = [
847 | "proc-macro2",
848 | "quote",
849 | "syn 2.0.100",
850 | ]
851 |
852 | [[package]]
853 | name = "tokio-tun"
854 | version = "0.13.2"
855 | source = "registry+https://github.com/rust-lang/crates.io-index"
856 | checksum = "be25a316a2d10d0ddd46de9ddd73cdb4262678e1e52f4a32a31421f1e2b816b4"
857 | dependencies = [
858 | "libc",
859 | "nix",
860 | "thiserror",
861 | "tokio",
862 | ]
863 |
864 | [[package]]
865 | name = "tokio-util"
866 | version = "0.7.14"
867 | source = "registry+https://github.com/rust-lang/crates.io-index"
868 | checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
869 | dependencies = [
870 | "bytes",
871 | "futures-core",
872 | "futures-sink",
873 | "pin-project-lite",
874 | "tokio",
875 | ]
876 |
877 | [[package]]
878 | name = "unicode-ident"
879 | version = "1.0.18"
880 | source = "registry+https://github.com/rust-lang/crates.io-index"
881 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
882 |
883 | [[package]]
884 | name = "utf8parse"
885 | version = "0.2.2"
886 | source = "registry+https://github.com/rust-lang/crates.io-index"
887 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
888 |
889 | [[package]]
890 | name = "wasi"
891 | version = "0.11.0+wasi-snapshot-preview1"
892 | source = "registry+https://github.com/rust-lang/crates.io-index"
893 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
894 |
895 | [[package]]
896 | name = "wasi"
897 | version = "0.13.3+wasi-0.2.2"
898 | source = "registry+https://github.com/rust-lang/crates.io-index"
899 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
900 | dependencies = [
901 | "wit-bindgen-rt",
902 | ]
903 |
904 | [[package]]
905 | name = "wasm-bindgen"
906 | version = "0.2.100"
907 | source = "registry+https://github.com/rust-lang/crates.io-index"
908 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
909 | dependencies = [
910 | "cfg-if",
911 | "once_cell",
912 | "wasm-bindgen-macro",
913 | ]
914 |
915 | [[package]]
916 | name = "wasm-bindgen-backend"
917 | version = "0.2.100"
918 | source = "registry+https://github.com/rust-lang/crates.io-index"
919 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
920 | dependencies = [
921 | "bumpalo",
922 | "log",
923 | "proc-macro2",
924 | "quote",
925 | "syn 2.0.100",
926 | "wasm-bindgen-shared",
927 | ]
928 |
929 | [[package]]
930 | name = "wasm-bindgen-macro"
931 | version = "0.2.100"
932 | source = "registry+https://github.com/rust-lang/crates.io-index"
933 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
934 | dependencies = [
935 | "quote",
936 | "wasm-bindgen-macro-support",
937 | ]
938 |
939 | [[package]]
940 | name = "wasm-bindgen-macro-support"
941 | version = "0.2.100"
942 | source = "registry+https://github.com/rust-lang/crates.io-index"
943 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
944 | dependencies = [
945 | "proc-macro2",
946 | "quote",
947 | "syn 2.0.100",
948 | "wasm-bindgen-backend",
949 | "wasm-bindgen-shared",
950 | ]
951 |
952 | [[package]]
953 | name = "wasm-bindgen-shared"
954 | version = "0.2.100"
955 | source = "registry+https://github.com/rust-lang/crates.io-index"
956 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
957 | dependencies = [
958 | "unicode-ident",
959 | ]
960 |
961 | [[package]]
962 | name = "winapi"
963 | version = "0.3.9"
964 | source = "registry+https://github.com/rust-lang/crates.io-index"
965 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
966 | dependencies = [
967 | "winapi-i686-pc-windows-gnu",
968 | "winapi-x86_64-pc-windows-gnu",
969 | ]
970 |
971 | [[package]]
972 | name = "winapi-i686-pc-windows-gnu"
973 | version = "0.4.0"
974 | source = "registry+https://github.com/rust-lang/crates.io-index"
975 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
976 |
977 | [[package]]
978 | name = "winapi-util"
979 | version = "0.1.9"
980 | source = "registry+https://github.com/rust-lang/crates.io-index"
981 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
982 | dependencies = [
983 | "windows-sys 0.59.0",
984 | ]
985 |
986 | [[package]]
987 | name = "winapi-x86_64-pc-windows-gnu"
988 | version = "0.4.0"
989 | source = "registry+https://github.com/rust-lang/crates.io-index"
990 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
991 |
992 | [[package]]
993 | name = "windows-sys"
994 | version = "0.52.0"
995 | source = "registry+https://github.com/rust-lang/crates.io-index"
996 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
997 | dependencies = [
998 | "windows-targets",
999 | ]
1000 |
1001 | [[package]]
1002 | name = "windows-sys"
1003 | version = "0.59.0"
1004 | source = "registry+https://github.com/rust-lang/crates.io-index"
1005 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1006 | dependencies = [
1007 | "windows-targets",
1008 | ]
1009 |
1010 | [[package]]
1011 | name = "windows-targets"
1012 | version = "0.52.6"
1013 | source = "registry+https://github.com/rust-lang/crates.io-index"
1014 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1015 | dependencies = [
1016 | "windows_aarch64_gnullvm",
1017 | "windows_aarch64_msvc",
1018 | "windows_i686_gnu",
1019 | "windows_i686_gnullvm",
1020 | "windows_i686_msvc",
1021 | "windows_x86_64_gnu",
1022 | "windows_x86_64_gnullvm",
1023 | "windows_x86_64_msvc",
1024 | ]
1025 |
1026 | [[package]]
1027 | name = "windows_aarch64_gnullvm"
1028 | version = "0.52.6"
1029 | source = "registry+https://github.com/rust-lang/crates.io-index"
1030 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1031 |
1032 | [[package]]
1033 | name = "windows_aarch64_msvc"
1034 | version = "0.52.6"
1035 | source = "registry+https://github.com/rust-lang/crates.io-index"
1036 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1037 |
1038 | [[package]]
1039 | name = "windows_i686_gnu"
1040 | version = "0.52.6"
1041 | source = "registry+https://github.com/rust-lang/crates.io-index"
1042 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1043 |
1044 | [[package]]
1045 | name = "windows_i686_gnullvm"
1046 | version = "0.52.6"
1047 | source = "registry+https://github.com/rust-lang/crates.io-index"
1048 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1049 |
1050 | [[package]]
1051 | name = "windows_i686_msvc"
1052 | version = "0.52.6"
1053 | source = "registry+https://github.com/rust-lang/crates.io-index"
1054 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1055 |
1056 | [[package]]
1057 | name = "windows_x86_64_gnu"
1058 | version = "0.52.6"
1059 | source = "registry+https://github.com/rust-lang/crates.io-index"
1060 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1061 |
1062 | [[package]]
1063 | name = "windows_x86_64_gnullvm"
1064 | version = "0.52.6"
1065 | source = "registry+https://github.com/rust-lang/crates.io-index"
1066 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1067 |
1068 | [[package]]
1069 | name = "windows_x86_64_msvc"
1070 | version = "0.52.6"
1071 | source = "registry+https://github.com/rust-lang/crates.io-index"
1072 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1073 |
1074 | [[package]]
1075 | name = "wit-bindgen-rt"
1076 | version = "0.33.0"
1077 | source = "registry+https://github.com/rust-lang/crates.io-index"
1078 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
1079 | dependencies = [
1080 | "bitflags",
1081 | ]
1082 |
1083 | [[package]]
1084 | name = "zerocopy"
1085 | version = "0.8.23"
1086 | source = "registry+https://github.com/rust-lang/crates.io-index"
1087 | checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
1088 | dependencies = [
1089 | "zerocopy-derive",
1090 | ]
1091 |
1092 | [[package]]
1093 | name = "zerocopy-derive"
1094 | version = "0.8.23"
1095 | source = "registry+https://github.com/rust-lang/crates.io-index"
1096 | checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
1097 | dependencies = [
1098 | "proc-macro2",
1099 | "quote",
1100 | "syn 2.0.100",
1101 | ]
1102 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 |
3 | resolver = "2"
4 |
5 | members = [
6 | "fake-tcp",
7 | "phantun",
8 | ]
9 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2021-2024 Datong Sun (dndx@idndx.com)
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-2024 Datong Sun (dndx@idndx.com)
4 |
5 | Permission is hereby granted, free of charge, to any
6 | person obtaining a copy of this software and associated
7 | documentation files (the "Software"), to deal in the
8 | Software without restriction, including without
9 | limitation the rights to use, copy, modify, merge,
10 | publish, distribute, sublicense, and/or sell copies of
11 | the Software, and to permit persons to whom the Software
12 | is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice
16 | shall be included in all copies or substantial portions
17 | of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 | DEALINGS IN THE SOFTWARE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phantun
2 |
3 | A lightweight and fast UDP to TCP obfuscator.
4 |
5 | 
6 | 
7 |
8 | Table of Contents
9 | =================
10 |
11 | * [Phantun](#phantun)
12 | * [Latest release](#latest-release)
13 | * [Overview](#overview)
14 | * [Usage](#usage)
15 | * [1. Enable Kernel IP forwarding](#1-enable-kernel-ip-forwarding)
16 | * [2. Add required firewall rules](#2-add-required-firewall-rules)
17 | * [Client](#client)
18 | * [Using nftables](#using-nftables)
19 | * [Using iptables](#using-iptables)
20 | * [Server](#server)
21 | * [Using nftables](#using-nftables)
22 | * [Using iptables](#using-iptables)
23 | * [3. Run Phantun binaries as non-root (Optional)](#3-run-phantun-binaries-as-non-root-optional)
24 | * [4. Start Phantun daemon](#4-start-phantun-daemon)
25 | * [Server](#server)
26 | * [Client](#client)
27 | * [MTU overhead](#mtu-overhead)
28 | * [MTU calculation for WireGuard](#mtu-calculation-for-wireguard)
29 | * [Version compatibility](#version-compatibility)
30 | * [Documentations](#documentations)
31 | * [Performance](#performance)
32 | * [Future plans](#future-plans)
33 | * [Compariation to udp2raw](#compariation-to-udp2raw)
34 | * [License](#license)
35 |
36 | # Latest release
37 |
38 | [v0.7.0](https://github.com/dndx/phantun/releases/tag/v0.7.0)
39 |
40 | # Overview
41 |
42 | Phantun is a project that obfuscated UDP packets into TCP connections. It aims to
43 | achieve maximum performance with minimum processing and encapsulation overhead.
44 |
45 | It is commonly used in environments where UDP is blocked/throttled but TCP is allowed through.
46 |
47 | Phantun simply converts a stream of UDP packets into obfuscated TCP stream packets. The TCP stack
48 | used by Phantun is designed to pass through most L3/L4 stateful/stateless firewalls/NAT
49 | devices. It will **not** be able to pass through L7 proxies.
50 | However, the advantage of this approach is that none of the common UDP over TCP performance killer
51 | such as retransmissions and flow control will occur. The underlying UDP properties such as
52 | out-of-order delivery are fully preserved even if the connection ends up looking like a TCP
53 | connection from the perspective of firewalls/NAT devices.
54 |
55 | Phantun means Phantom TUN, as it is an obfuscator for UDP traffic that does just enough work
56 | to make it pass through stateful firewall/NATs as TCP packets.
57 |
58 | Phantun is written in 100% safe Rust. It has been optimized extensively to scale well on multi-core
59 | systems and has no issue saturating all available CPU resources on a fast connection.
60 | See the [Performance](#performance) section for benchmarking results.
61 |
62 | 
63 | 
64 |
65 | # Usage
66 |
67 | For the example below, it is assumed that **Phantun Server** listens for incoming Phantun Client connections at
68 | port `4567` (the `--local` option for server), and it forwards UDP packets to UDP server at `127.0.0.1:1234`
69 | (the `--remote` option for server).
70 |
71 | It is also assumed that **Phantun Client** listens for incoming UDP packets at
72 | `127.0.0.1:1234` (the `--local` option for client) and connects to Phantun Server at `10.0.0.1:4567`
73 | (the `--remote` option for client).
74 |
75 | Phantun creates TUN interface for both the Client and Server. For **Client**, Phantun assigns itself the IP address
76 | `192.168.200.2` and `fcc8::2` by default.
77 | For **Server**, it assigns `192.168.201.2` and `fcc9::2` by default. Therefore, your Kernel must have
78 | IPv4/IPv6 forwarding enabled and setup appropriate iptables/nftables rules for NAT between your physical
79 | NIC address and Phantun's Tun interface address.
80 |
81 | You may customize the name of Tun interface created by Phantun and the assigned addresses. Please
82 | run the executable with `-h` options to see how to change them.
83 |
84 | Another way to help understand this network topology (please see the diagram above for an illustration of this topology):
85 |
86 | Phantun Client is like a machine with private IP address (`192.168.200.2`/`fcc8::2`) behind a router.
87 | In order for it to reach the Internet, you will need to SNAT the private IP address before it's traffic
88 | leaves the NIC.
89 |
90 | Phantun Server is like a server with private IP address (`192.168.201.2`/`fcc9::2`) behind a router.
91 | In order to access it from the Internet, you need to `DNAT` it's listening port on the router
92 | and change the destination IP address to where the server is listening for incoming connections.
93 |
94 | In those cases, the machine/iptables running Phantun acts as the "router" that allows Phantun
95 | to communicate with outside using it's private IP addresses.
96 |
97 | As of Phantun v0.4.1, IPv6 is fully supported for both TCP and UDP sides.
98 | To specify an IPv6 address, use the following format: `[::1]:1234` with
99 | the command line options. Resolving AAAA record is also supported. Please run the program
100 | with `-h` to see detailed options on how to control the IPv6 behavior.
101 |
102 | [Back to TOC](#table-of-contents)
103 |
104 | ## 1. Enable Kernel IP forwarding
105 |
106 | Edit `/etc/sysctl.conf`, add `net.ipv4.ip_forward=1` and run `sudo sysctl -p /etc/sysctl.conf`.
107 |
108 |
109 | IPv6 specific config
110 |
111 | `net.ipv6.conf.all.forwarding=1` will need to be set as well.
112 |
113 |
114 | [Back to TOC](#table-of-contents)
115 |
116 | ## 2. Add required firewall rules
117 |
118 |
119 | ### Client
120 |
121 | Client simply need SNAT enabled on the physical interface to translate Phantun's address into
122 | one that can be used on the physical network. This can be done simply with masquerade.
123 |
124 | Note: change `eth0` to whatever actual physical interface name is
125 |
126 | [Back to TOC](#table-of-contents)
127 |
128 | #### Using nftables
129 |
130 | ```
131 | table inet nat {
132 | chain postrouting {
133 | type nat hook postrouting priority srcnat; policy accept;
134 | iifname tun0 oif eth0 masquerade
135 | }
136 | }
137 | ```
138 |
139 | Note: The above rule uses `inet` as the table family type, so it is compatible with
140 | both IPv4 and IPv6 usage.
141 |
142 | [Back to TOC](#table-of-contents)
143 |
144 | #### Using iptables
145 |
146 | ```
147 | iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
148 | ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
149 | ```
150 |
151 | [Back to TOC](#table-of-contents)
152 |
153 | ### Server
154 |
155 | Server needs to DNAT the TCP listening port to Phantun's TUN interface address.
156 |
157 | Note: change `eth0` to whatever actual physical interface name is and `4567` to
158 | actual TCP port number used by Phantun server
159 |
160 | [Back to TOC](#table-of-contents)
161 |
162 | #### Using nftables
163 |
164 | ```
165 | table inet nat {
166 | chain prerouting {
167 | type nat hook prerouting priority dstnat; policy accept;
168 | iif eth0 tcp dport 4567 dnat ip to 192.168.201.2
169 | iif eth0 tcp dport 4567 dnat ip6 to fcc9::2
170 | }
171 | }
172 | ```
173 |
174 | [Back to TOC](#table-of-contents)
175 |
176 | #### Using iptables
177 |
178 | ```
179 | iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination 192.168.201.2
180 | ip6tables -t nat -A PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination fcc9::2
181 | ```
182 |
183 | [Back to TOC](#table-of-contents)
184 |
185 | ## 3. Run Phantun binaries as non-root (Optional)
186 |
187 | It is ill-advised to run network facing applications as root user. Phantun can be run fully
188 | as non-root user with the `cap_net_admin` capability.
189 |
190 | ```
191 | sudo setcap cap_net_admin=+pe phantun_server
192 | sudo setcap cap_net_admin=+pe phantun_client
193 | ```
194 |
195 |
196 | [Back to TOC](#table-of-contents)
197 |
198 | ## 4. Start Phantun daemon
199 |
200 | **Note:** Run Phantun executable with `-h` option to see full detailed options.
201 |
202 | [Back to TOC](#table-of-contents)
203 |
204 | ### Server
205 |
206 | Note: `4567` is the TCP port Phantun should listen on and must corresponds to the DNAT
207 | rule specified above. `127.0.0.1:1234` is the UDP Server to connect to for new connections.
208 |
209 | ```
210 | RUST_LOG=info /usr/local/bin/phantun_server --local 4567 --remote 127.0.0.1:1234
211 | ```
212 |
213 | Or use host name with `--remote`:
214 |
215 | ```
216 | RUST_LOG=info /usr/local/bin/phantun_server --local 4567 --remote example.com:1234
217 | ```
218 |
219 | Note: Server by default assigns both IPv4 and IPv6 private address to the Tun interface.
220 | If you do not wish to use IPv6, you can simply skip creating the IPv6 DNAT rule above and
221 | the presence of IPv6 address on the Tun interface should have no side effect to the server.
222 |
223 | [Back to TOC](#table-of-contents)
224 |
225 | ### Client
226 |
227 | Note: `127.0.0.1:1234` is the UDP address and port Phantun should listen on. `10.0.0.1:4567` is
228 | the Phantun Server to connect.
229 |
230 | ```
231 | RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote 10.0.0.1:4567
232 | ```
233 |
234 | Or use host name with `--remote`:
235 |
236 | ```
237 | RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote example.com:4567
238 | ```
239 |
240 |
241 | IPv6 specific config
242 |
243 | ```
244 | RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote [fdxx::1234]:4567
245 | ```
246 |
247 | Domain name with AAAA record is also supported.
248 |
249 |
250 | [Back to TOC](#table-of-contents)
251 |
252 | # MTU overhead
253 |
254 | Phantun aims to keep tunneling overhead to the minimum. The overhead compared to a plain UDP packet
255 | is the following (using IPv4 below as an example):
256 |
257 | **Standard UDP packet:** `20 byte IP header + 8 byte UDP header = 28 bytes`
258 |
259 | **Obfuscated packet:** `20 byte IP header + 20 byte TCP header = 40 bytes`
260 |
261 |
262 | Note that Phantun does not add any additional header other than IP and TCP headers in order to pass through
263 | stateful packet inspection!
264 |
265 | Phantun's additional overhead: `12 bytes`. In other words, when using Phantun, the usable payload for
266 | UDP packet is reduced by 12 bytes. This is the minimum overhead possible when doing such kind
267 | of obfuscation.
268 |
269 | 
270 |
271 | [Back to TOC](#table-of-contents)
272 |
273 | ## MTU calculation for WireGuard
274 |
275 | For people who use Phantun to tunnel [WireGuard®](https://www.wireguard.com) UDP packets, here are some guidelines on figuring
276 | out the correct MTU to use for your WireGuard interface.
277 |
278 | ```
279 | WireGuard MTU = Interface MTU - IPv4 header (20 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes)
280 | ```
281 |
282 | or
283 |
284 | ```
285 | WireGuard MTU = Interface MTU - IPv6 header (40 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes)
286 | ```
287 |
288 | For example, for a Ethernet interface with 1500 bytes MTU, the WireGuard interface MTU should be set as:
289 |
290 | IPv4: `1500 - 20 - 20 - 32 = 1428 bytes`
291 | IPv6: `1500 - 40 - 20 - 32 = 1408 bytes`
292 |
293 | The resulted Phantun TCP data packet will be 1500 bytes which does not exceed the
294 | interface MTU of 1500. Please note it is strongly recommended to use the same interface
295 | MTU for both ends of a WireGuard tunnel, or unexpected packet loss may occur and these issues are
296 | generally very hard to troubleshoot.
297 |
298 | [Back to TOC](#table-of-contents)
299 |
300 | # Version compatibility
301 |
302 | While the TCP stack is fairly stable, the general expectation is that you should run same minor versions
303 | of Server/Client of Phantun on both ends to ensure maximum compatibility.
304 |
305 | [Back to TOC](#table-of-contents)
306 |
307 | # Documentations
308 |
309 | For users who wish to use `fake-tcp` library inside their own project, refer to the documentations for the library at:
310 | [https://docs.rs/fake-tcp](https://docs.rs/fake-tcp).
311 |
312 | [Back to TOC](#table-of-contents)
313 |
314 | # Performance
315 |
316 | Performance was tested on 2 AWS `t4g.xlarge` instances with 4 vCPUs and 5 Gb/s NIC over LAN. `nftables` was used to redirect
317 | UDP stream of `iperf3` to go through the Phantun/udp2raw tunnel between two test instances and MTU has been tuned to avoid fragmentation.
318 |
319 | Phantun `v0.3.2` and `udp2raw_arm_asm_aes` `20200818.0` was used. These were the latest release of both projects as of Apr 2022.
320 |
321 | Test command: `iperf3 -c -p -R -u -l 1400 -b 1000m -t 30 -P 5`
322 |
323 | | Mode | Send Speed | Receive Speed | Overall CPU Usage |
324 | |---------------------------------------------------------------------------------|----------------|----------------|-----------------------------------------------------|
325 | | Direct (1 stream) | 3.00 Gbits/sec | 2.37 Gbits/sec | 25% (1 core at 100%) |
326 | | Phantun (1 stream) | 1.30 Gbits/sec | 1.20 Gbits/sec | 60% (1 core at 100%, 3 cores at 50%) |
327 | | udp2raw (`cipher-mode=none` `auth-mode=none` `disable-anti-replay`) (1 stream) | 1.30 Gbits/sec | 715 Mbits/sec | 40% (1 core at 100%, 1 core at 50%, 2 cores idling) |
328 | | Direct connection (5 streams) | 5.00 Gbits/sec | 3.64 Gbits/sec | 25% (1 core at 100%) |
329 | | Phantun (5 streams) | 5.00 Gbits/sec | 2.38 Gbits/sec | 95% (all cores utilized) |
330 | | udp2raw (`cipher-mode=none` `auth-mode=none` `disable-anti-replay`) (5 streams) | 5.00 Gbits/sec | 770 Mbits/sec | 50% (2 cores at 100%) |
331 |
332 | Writeup on some of the techniques used in Phantun to achieve this performance result: [Writing Highly Efficient UDP Server in Rust](https://idndx.com/writing-highly-efficient-udp-server-in-rust/).
333 |
334 | [Back to TOC](#table-of-contents)
335 |
336 | # Future plans
337 |
338 | * Load balancing a single UDP stream into multiple TCP streams
339 | * Integration tests
340 | * Auto insertion/removal of required firewall rules
341 |
342 | [Back to TOC](#table-of-contents)
343 |
344 | # Compariation to udp2raw
345 | [udp2raw](https://github.com/wangyu-/udp2raw-tunnel) is another popular project by [@wangyu-](https://github.com/wangyu-)
346 | that is very similar to what Phantun can do. In fact I took inspirations of Phantun from udp2raw. The biggest reason for
347 | developing Phantun is because of lack of performance when running udp2raw (especially on multi-core systems such as Raspberry Pi).
348 | However, the goal is never to be as feature complete as udp2raw and only support the most common use cases. Most notably, UDP over ICMP
349 | and UDP over UDP mode are not supported and there is no anti-replay nor encryption support. The benefit of this is much better
350 | performance overall and less MTU overhead because lack of additional headers inside the TCP payload.
351 |
352 | Here is a quick overview of comparison between those two to help you choose:
353 |
354 | | | Phantun | udp2raw |
355 | |--------------------------------------------------|:-------------:|:-----------------:|
356 | | UDP over FakeTCP obfuscation | ✅ | ✅ |
357 | | UDP over ICMP obfuscation | ❌ | ✅ |
358 | | UDP over UDP obfuscation | ❌ | ✅ |
359 | | Multi-threaded | ✅ | ❌ |
360 | | Throughput | Better | Good |
361 | | Layer 3 mode | TUN interface | Raw sockets + BPF |
362 | | Tunneling MTU overhead | 12 bytes | 44 bytes |
363 | | Seprate TCP connections for each UDP connection | Client/Server | Server only |
364 | | Anti-replay, encryption | ❌ | ✅ |
365 | | IPv6 | ✅ | ✅ |
366 |
367 | [Back to TOC](#table-of-contents)
368 |
369 | # License
370 |
371 | Copyright 2021-2024 Datong Sun (dndx@idndx.com)
372 |
373 | Licensed under the Apache License, Version 2.0 or the MIT license
375 | , at your
376 | option. Files in the project may not be
377 | copied, modified, or distributed except according to those terms.
378 |
379 | [Back to TOC](#table-of-contents)
380 |
381 |
--------------------------------------------------------------------------------
/docker/.dockerignore:
--------------------------------------------------------------------------------
1 | README.md
2 | docker-compose.yml
3 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | #
2 | # Dockerfile for phantun
3 | #
4 |
5 | #
6 | # Build stage
7 | #
8 | FROM rust:latest AS builder
9 |
10 | COPY . /phantun
11 |
12 | RUN cd phantun \
13 | && cargo build --release \
14 | && strip target/release/server target/release/client \
15 | && install target/release/server /usr/local/bin/phantun-server \
16 | && install target/release/client /usr/local/bin/phantun-client \
17 | && cd - \
18 | && rm -r phantun
19 |
20 | #
21 | # Runtime stage
22 | #
23 | FROM debian:latest
24 |
25 | COPY --from=builder /usr/local/bin/phantun-server /usr/local/bin/
26 | COPY --from=builder /usr/local/bin/phantun-client /usr/local/bin/
27 | COPY docker/phantun.sh /usr/local/bin/
28 |
29 | ENV USE_IPTABLES_NFT_BACKEND=0
30 | ENV RUST_LOG=INFO
31 |
32 | ENTRYPOINT ["phantun.sh"]
33 | CMD ["phantun-server", "--help"]
34 |
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 | # phantun (docker)
2 |
3 | ## Build
4 |
5 | ```sh
6 | docker build -t phantun -f docker/Dockerfile .
7 | ```
8 |
9 | ## Usage
10 |
11 | It is recommended to use docker-compose, see [docker-compose.yml](docker-compose.yml) for details.
12 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | phantun-server:
5 | image: phantun
6 | container_name: phantun-server
7 | restart: unless-stopped
8 | network_mode: host
9 | privileged: true
10 | environment:
11 | USE_IPTABLES_NFT_BACKEND: 0
12 | RUST_LOG: INFO
13 | command: >
14 | phantun-server --local 1985 --remote 127.0.0.1:1984 --ipv4-only
15 |
16 | phantun-client:
17 | image: phantun
18 | container_name: phantun-client
19 | restart: unless-stopped
20 | network_mode: host
21 | privileged: true
22 | environment:
23 | USE_IPTABLES_NFT_BACKEND: 0
24 | RUST_LOG: INFO
25 | command: >
26 | phantun-client --local 127.0.0.1:1984 --remote 11.22.33.44:1985 --ipv4-only
27 |
--------------------------------------------------------------------------------
/docker/phantun.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # alias settings must be global, and must be defined before the function being called with the alias
4 | if [ "$USE_IPTABLES_NFT_BACKEND" = 1 ]; then
5 | alias iptables=iptables-nft
6 | alias iptables-save=iptables-nft-save
7 | alias ip6tables=ip6tables-nft
8 | alias ip6tables-save=ip6tables-nft-save
9 | fi
10 |
11 | info() {
12 | local green='\e[0;32m'
13 | local clear='\e[0m'
14 | local time=$(date '+%Y-%m-%d %T')
15 | printf "${green}[${time}] [INFO]: ${clear}%s\n" "$*"
16 | }
17 |
18 | warn() {
19 | local yellow='\e[1;33m'
20 | local clear='\e[0m'
21 | local time=$(date '+%Y-%m-%d %T')
22 | printf "${yellow}[${time}] [WARN]: ${clear}%s\n" "$*" >&2
23 | }
24 |
25 | error() {
26 | local red='\e[0;31m'
27 | local clear='\e[0m'
28 | local time=$(date '+%Y-%m-%d %T')
29 | printf "${red}[${time}] [ERROR]: ${clear}%s\n" "$*" >&2
30 | }
31 |
32 | _get_default_iface() {
33 | ip -4 route show default | awk -F 'dev' '{print $2}' | awk '{print $1}'
34 | }
35 |
36 | _get_default6_iface() {
37 | ip -6 route show default | awk -F 'dev' '{print $2}' | awk '{print $1}'
38 | }
39 |
40 | _get_addr_by_iface() {
41 | ip -4 addr show dev "$1" | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | head -1
42 | }
43 |
44 | _get_addr6_by_iface() {
45 | ip -6 addr show dev "$1" | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | head -1
46 | }
47 |
48 | _check_rule_by_comment() {
49 | iptables-save | grep -q "$1"
50 | }
51 |
52 | _check_rule6_by_comment() {
53 | ip6tables-save | grep -q "$1"
54 | }
55 |
56 | _is_server_mode() {
57 | [ "$1" = "phantun-server" ]
58 | }
59 |
60 | _is_ipv4_only() {
61 | case "$@" in
62 | *-4*|*--ipv4-only*)
63 | return 0
64 | ;;
65 | *\ -4*|*\ --ipv4-only*)
66 | return 0
67 | ;;
68 | esac
69 | return 1
70 | }
71 |
72 | _get_tun_from_args() {
73 | local tun=$(echo "$@" | awk -F '--tun' '{print $2}' | awk '{print $1}')
74 | echo ${tun:=tun0}
75 | }
76 |
77 | _get_peer_from_args() {
78 | local peer=$(echo "$@" | awk -F '--tun-peer' '{print $2}' | awk '{print $1}')
79 | _is_server_mode "$1" && echo ${peer:=192.168.201.2} || echo ${peer:=192.168.200.2}
80 | }
81 |
82 | _get_peer6_from_args() {
83 | local peer=$(echo "$@" | awk -F '--tun-peer6' '{print $2}' | awk '{print $1}')
84 | _is_server_mode "$1" && echo ${peer:=fcc9::2} || echo ${peer:=fcc8::2}
85 | }
86 |
87 | _get_port_from_args() {
88 | local value=$(echo "$@" | awk -F '-l|--local' '{print $2}' | awk '{print $1}')
89 | _is_server_mode "$1" && echo $value || echo $value | awk -F ':' '{print $2}'
90 | }
91 |
92 | _iptables() {
93 | iptables -w 10 "$@"
94 | }
95 |
96 | _ip6tables() {
97 | ip6tables -w 10 "$@"
98 | }
99 |
100 | apply_sysctl() {
101 | info "apply sysctl: $(sysctl -w net.ipv4.ip_forward=1)"
102 | ! _is_ipv4_only "$@" || return
103 | info "apply sysctl: $(sysctl -w net.ipv6.conf.all.forwarding=1)"
104 | }
105 |
106 | apply_iptables() {
107 | local interface=$(_get_default_iface)
108 | local address=$(_get_addr_by_iface "${interface}")
109 | local tun=$(_get_tun_from_args "$@")
110 | local peer=$(_get_peer_from_args "$@")
111 | local port=$(_get_port_from_args "$@")
112 | local comment="phantun_${tun}_${port}"
113 |
114 | if _check_rule_by_comment "${comment}"; then
115 | warn "iptables rules already exist, maybe needs to check."
116 | else
117 | _iptables -A FORWARD -i $tun -j ACCEPT -m comment --comment "${comment}" || error "iptables filter rule add failed."
118 | _iptables -A FORWARD -o $tun -j ACCEPT -m comment --comment "${comment}" || error "iptables filter rule add failed."
119 | if _is_server_mode "$1"; then
120 | info "iptables DNAT rule added: [${comment}]: ${interface} -> ${tun}, ${address} -> ${peer}"
121 | _iptables -t nat -A PREROUTING -p tcp -i $interface --dport $port -j DNAT --to-destination $peer \
122 | -m comment --comment "${comment}" || error "iptables DNAT rule add failed."
123 | else
124 | info "iptables SNAT rule added: [${comment}]: ${tun} -> ${interface}, ${peer} -> ${address}"
125 | _iptables -t nat -A POSTROUTING -s $peer -o $interface -j SNAT --to-source $address \
126 | -m comment --comment "${comment}" || error "iptables SNAT rule add failed."
127 | fi
128 | fi
129 | }
130 |
131 | apply_ip6tables() {
132 | ! _is_ipv4_only "$@" || return
133 |
134 | local interface=$(_get_default6_iface)
135 | local address=$(_get_addr6_by_iface "${interface}")
136 | local tun=$(_get_tun_from_args "$@")
137 | local peer=$(_get_peer6_from_args "$@")
138 | local port=$(_get_port_from_args "$@")
139 | local comment="phantun_${tun}_${port}"
140 |
141 | if _check_rule6_by_comment "${comment}"; then
142 | warn "ip6tables rules already exist, maybe needs to check."
143 | else
144 | _ip6tables -A FORWARD -i $tun -j ACCEPT -m comment --comment "${comment}" || error "ip6tables filter rule add failed."
145 | _ip6tables -A FORWARD -o $tun -j ACCEPT -m comment --comment "${comment}" || error "ip6tables filter rule add failed."
146 | if _is_server_mode "$1"; then
147 | info "ip6tables DNAT rule added: [${comment}]: ${interface} -> ${tun}, ${address} -> ${peer}"
148 | _ip6tables -t nat -A PREROUTING -p tcp -i $interface --dport $port -j DNAT --to-destination $peer \
149 | -m comment --comment "${comment}" || error "ip6tables DNAT rule add failed."
150 | else
151 | info "ip6tables SNAT rule added: [${comment}]: ${tun} -> ${interface}, ${peer} -> ${address}"
152 | _ip6tables -t nat -A POSTROUTING -s $peer -o $interface -j SNAT --to-source $address \
153 | -m comment --comment "${comment}" || error "ip6tables SNAT rule add failed."
154 | fi
155 | fi
156 | }
157 |
158 | stop_process() {
159 | kill $(pidof phantun-server phantun-client)
160 | info "terminate phantun process."
161 | }
162 |
163 | revoke_iptables() {
164 | local tun=$(_get_tun_from_args "$@")
165 | local port=$(_get_port_from_args "$@")
166 | local comment="phantun_${tun}_${port}"
167 |
168 | iptables-save -t filter | grep "${comment}" | while read rule; do
169 | _iptables -t filter ${rule/-A/-D} || error "iptables filter rule remove failed."
170 | done
171 | iptables-save -t nat | grep "${comment}" | while read rule; do
172 | _iptables -t nat ${rule/-A/-D} || error "iptables nat rule remove failed."
173 | done
174 | info "iptables rule: [${comment}] removed."
175 | }
176 |
177 | revoke_ip6tables() {
178 | ! _is_ipv4_only "$@" || return
179 |
180 | local tun=$(_get_tun_from_args "$@")
181 | local port=$(_get_port_from_args "$@")
182 | local comment="phantun_${tun}_${port}"
183 |
184 | ip6tables-save -t filter | grep "${comment}" | while read rule; do
185 | _ip6tables -t filter ${rule/-A/-D} || error "ip6tables filter rule remove failed."
186 | done
187 | ip6tables-save -t nat | grep "${comment}" | while read rule; do
188 | _ip6tables -t nat ${rule/-A/-D} || error "ip6tables nat rule remove failed."
189 | done
190 | info "ip6tables rule: [${comment}] removed."
191 | }
192 |
193 | graceful_stop() {
194 | warn "caught SIGTERM or SIGINT signal, graceful stopping..."
195 | stop_process
196 | revoke_iptables "$@"
197 | revoke_ip6tables "$@"
198 | }
199 |
200 | start_phantun() {
201 | trap 'graceful_stop "$@"' SIGTERM SIGINT
202 | apply_sysctl "$@"
203 | apply_iptables "$@"
204 | apply_ip6tables "$@"
205 | "$@" &
206 | wait
207 | }
208 |
209 | start_phantun "$@"
210 |
--------------------------------------------------------------------------------
/fake-tcp/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "fake-tcp"
3 | version = "0.6.0"
4 | edition = "2021"
5 | authors = ["Datong Sun "]
6 | license = "MIT OR Apache-2.0"
7 | repository = "https://github.com/dndx/phantun"
8 | readme = "README.md"
9 | description = """
10 | A TUN interface based, user space, asynchronous and high performance TCP stack that allows
11 | packet oriented tunneling with minimum overhead.
12 | """
13 |
14 | [features]
15 | benchmark = []
16 |
17 | [dependencies]
18 | bytes = "1"
19 | pnet = "0"
20 | tokio = { version = "1", features = ["full"] }
21 | rand = { version = "0", features = ["small_rng"] }
22 | log = "0"
23 | internet-checksum = "0"
24 | tokio-tun = "0"
25 | flume = "0"
26 |
--------------------------------------------------------------------------------
/fake-tcp/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2021-2024 Datong Sun (dndx@idndx.com)
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/fake-tcp/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-2024 Datong Sun (dndx@idndx.com)
4 |
5 | Permission is hereby granted, free of charge, to any
6 | person obtaining a copy of this software and associated
7 | documentation files (the "Software"), to deal in the
8 | Software without restriction, including without
9 | limitation the rights to use, copy, modify, merge,
10 | publish, distribute, sublicense, and/or sell copies of
11 | the Software, and to permit persons to whom the Software
12 | is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice
16 | shall be included in all copies or substantial portions
17 | of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 | DEALINGS IN THE SOFTWARE.
28 |
--------------------------------------------------------------------------------
/fake-tcp/README.md:
--------------------------------------------------------------------------------
1 | # fake-tcp
2 |
3 | A TUN interface based, user space, asynchronous and high performance TCP stack that allows
4 | packet oriented tunneling with minimum overhead.
5 |
6 | ## License
7 |
8 | Copyright 2021 Datong Sun
9 |
10 | Licensed under the Apache License, Version 2.0 or the MIT license
12 | , at your
13 | option. Files in the project may not be
14 | copied, modified, or distributed except according to those terms.
15 |
--------------------------------------------------------------------------------
/fake-tcp/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! A minimum, userspace TCP based datagram stack
2 | //!
3 | //! # Overview
4 | //!
5 | //! `fake-tcp` is a reusable library that implements a minimum TCP stack in
6 | //! user space using the Tun interface. It allows programs to send datagrams
7 | //! as if they are part of a TCP connection. `fake-tcp` has been tested to
8 | //! be able to pass through a variety of NAT and stateful firewalls while
9 | //! fully preserves certain desirable behavior such as out of order delivery
10 | //! and no congestion/flow controls.
11 | //!
12 | //! # Core Concepts
13 | //!
14 | //! The core of the `fake-tcp` crate compose of two structures. [`Stack`] and
15 | //! [`Socket`].
16 | //!
17 | //! ## [`Stack`]
18 | //!
19 | //! [`Stack`] represents a virtual TCP stack that operates at
20 | //! Layer 3. It is responsible for:
21 | //!
22 | //! * TCP active and passive open and handshake
23 | //! * `RST` handling
24 | //! * Interact with the Tun interface at Layer 3
25 | //! * Distribute incoming datagrams to corresponding [`Socket`]
26 | //!
27 | //! ## [`Socket`]
28 | //!
29 | //! [`Socket`] represents a TCP connection. It registers the identifying
30 | //! tuple `(src_ip, src_port, dest_ip, dest_port)` inside the [`Stack`] so
31 | //! so that incoming packets can be distributed to the right [`Socket`] with
32 | //! using a channel. It is also what the client should use for
33 | //! sending/receiving datagrams.
34 | //!
35 | //! # Examples
36 | //!
37 | //! Please see [`client.rs`](https://github.com/dndx/phantun/blob/main/phantun/src/bin/client.rs)
38 | //! and [`server.rs`](https://github.com/dndx/phantun/blob/main/phantun/src/bin/server.rs) files
39 | //! from the `phantun` crate for how to use this library in client/server mode, respectively.
40 |
41 | #![cfg_attr(feature = "benchmark", feature(test))]
42 |
43 | pub mod packet;
44 |
45 | use bytes::{Bytes, BytesMut};
46 | use log::{error, info, trace, warn};
47 | use packet::*;
48 | use pnet::packet::{tcp, Packet};
49 | use rand::prelude::*;
50 | use std::collections::{HashMap, HashSet};
51 | use std::fmt;
52 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
53 | use std::sync::{
54 | atomic::{AtomicU32, Ordering},
55 | Arc, RwLock,
56 | };
57 | use tokio::sync::broadcast;
58 | use tokio::sync::mpsc;
59 | use tokio::time;
60 | use tokio_tun::Tun;
61 |
62 | const TIMEOUT: time::Duration = time::Duration::from_secs(1);
63 | const RETRIES: usize = 6;
64 | const MPMC_BUFFER_LEN: usize = 512;
65 | const MPSC_BUFFER_LEN: usize = 128;
66 | const MAX_UNACKED_LEN: u32 = 128 * 1024 * 1024; // 128MB
67 |
68 | #[derive(Hash, Eq, PartialEq, Clone, Debug)]
69 | struct AddrTuple {
70 | local_addr: SocketAddr,
71 | remote_addr: SocketAddr,
72 | }
73 |
74 | impl AddrTuple {
75 | fn new(local_addr: SocketAddr, remote_addr: SocketAddr) -> AddrTuple {
76 | AddrTuple {
77 | local_addr,
78 | remote_addr,
79 | }
80 | }
81 | }
82 |
83 | struct Shared {
84 | tuples: RwLock>>,
85 | listening: RwLock>,
86 | tun: Vec>,
87 | ready: mpsc::Sender,
88 | tuples_purge: broadcast::Sender,
89 | }
90 |
91 | pub struct Stack {
92 | shared: Arc,
93 | local_ip: Ipv4Addr,
94 | local_ip6: Option,
95 | ready: mpsc::Receiver,
96 | }
97 |
98 | pub enum State {
99 | Idle,
100 | SynSent,
101 | SynReceived,
102 | Established,
103 | }
104 |
105 | pub struct Socket {
106 | shared: Arc,
107 | tun: Arc,
108 | incoming: flume::Receiver,
109 | local_addr: SocketAddr,
110 | remote_addr: SocketAddr,
111 | seq: AtomicU32,
112 | ack: AtomicU32,
113 | last_ack: AtomicU32,
114 | state: State,
115 | }
116 |
117 | /// A socket that represents a unique TCP connection between a server and client.
118 | ///
119 | /// The `Socket` object itself satisfies `Sync` and `Send`, which means it can
120 | /// be safely called within an async future.
121 | ///
122 | /// To close a TCP connection that is no longer needed, simply drop this object
123 | /// out of scope.
124 | impl Socket {
125 | fn new(
126 | shared: Arc,
127 | tun: Arc,
128 | local_addr: SocketAddr,
129 | remote_addr: SocketAddr,
130 | ack: Option,
131 | state: State,
132 | ) -> (Socket, flume::Sender) {
133 | let (incoming_tx, incoming_rx) = flume::bounded(MPMC_BUFFER_LEN);
134 |
135 | (
136 | Socket {
137 | shared,
138 | tun,
139 | incoming: incoming_rx,
140 | local_addr,
141 | remote_addr,
142 | seq: AtomicU32::new(0),
143 | ack: AtomicU32::new(ack.unwrap_or(0)),
144 | last_ack: AtomicU32::new(ack.unwrap_or(0)),
145 | state,
146 | },
147 | incoming_tx,
148 | )
149 | }
150 |
151 | fn build_tcp_packet(&self, flags: u8, payload: Option<&[u8]>) -> Bytes {
152 | let ack = self.ack.load(Ordering::Relaxed);
153 | self.last_ack.store(ack, Ordering::Relaxed);
154 |
155 | build_tcp_packet(
156 | self.local_addr,
157 | self.remote_addr,
158 | self.seq.load(Ordering::Relaxed),
159 | ack,
160 | flags,
161 | payload,
162 | )
163 | }
164 |
165 | /// Sends a datagram to the other end.
166 | ///
167 | /// This method takes `&self`, and it can be called safely by multiple threads
168 | /// at the same time.
169 | ///
170 | /// A return of `None` means the Tun socket returned an error
171 | /// and this socket must be closed.
172 | pub async fn send(&self, payload: &[u8]) -> Option<()> {
173 | match self.state {
174 | State::Established => {
175 | let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, Some(payload));
176 | self.seq.fetch_add(payload.len() as u32, Ordering::Relaxed);
177 | self.tun.send(&buf).await.ok().and(Some(()))
178 | }
179 | _ => unreachable!(),
180 | }
181 | }
182 |
183 | /// Attempt to receive a datagram from the other end.
184 | ///
185 | /// This method takes `&self`, and it can be called safely by multiple threads
186 | /// at the same time.
187 | ///
188 | /// A return of `None` means the TCP connection is broken
189 | /// and this socket must be closed.
190 | pub async fn recv(&self, buf: &mut [u8]) -> Option {
191 | match self.state {
192 | State::Established => {
193 | self.incoming.recv_async().await.ok().and_then(|raw_buf| {
194 | let (_v4_packet, tcp_packet) = parse_ip_packet(&raw_buf).unwrap();
195 |
196 | if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
197 | info!("Connection {} reset by peer", self);
198 | return None;
199 | }
200 |
201 | let payload = tcp_packet.payload();
202 |
203 | let new_ack = tcp_packet.get_sequence().wrapping_add(payload.len() as u32);
204 | let last_ask = self.last_ack.load(Ordering::Relaxed);
205 | self.ack.store(new_ack, Ordering::Relaxed);
206 |
207 | if new_ack.overflowing_sub(last_ask).0 > MAX_UNACKED_LEN {
208 | let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, None);
209 | if let Err(e) = self.tun.try_send(&buf) {
210 | // This should not really happen as we have not sent anything for
211 | // quite some time...
212 | info!("Connection {} unable to send idling ACK back: {}", self, e)
213 | }
214 | }
215 |
216 | buf[..payload.len()].copy_from_slice(payload);
217 |
218 | Some(payload.len())
219 | })
220 | }
221 | _ => unreachable!(),
222 | }
223 | }
224 |
225 | async fn accept(mut self) {
226 | for _ in 0..RETRIES {
227 | match self.state {
228 | State::Idle => {
229 | let buf = self.build_tcp_packet(tcp::TcpFlags::SYN | tcp::TcpFlags::ACK, None);
230 | // ACK set by constructor
231 | self.tun.send(&buf).await.unwrap();
232 | self.state = State::SynReceived;
233 | info!("Sent SYN + ACK to client");
234 | }
235 | State::SynReceived => {
236 | let res = time::timeout(TIMEOUT, self.incoming.recv_async()).await;
237 | if let Ok(buf) = res {
238 | let buf = buf.unwrap();
239 | let (_v4_packet, tcp_packet) = parse_ip_packet(&buf).unwrap();
240 |
241 | if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
242 | return;
243 | }
244 |
245 | if tcp_packet.get_flags() == tcp::TcpFlags::ACK
246 | && tcp_packet.get_acknowledgement()
247 | == self.seq.load(Ordering::Relaxed) + 1
248 | {
249 | // found our ACK
250 | self.seq.fetch_add(1, Ordering::Relaxed);
251 | self.state = State::Established;
252 |
253 | info!("Connection from {:?} established", self.remote_addr);
254 | let ready = self.shared.ready.clone();
255 | if let Err(e) = ready.send(self).await {
256 | error!("Unable to send accepted socket to ready queue: {}", e);
257 | }
258 | return;
259 | }
260 | } else {
261 | info!("Waiting for client ACK timed out");
262 | self.state = State::Idle;
263 | }
264 | }
265 | _ => unreachable!(),
266 | }
267 | }
268 | }
269 |
270 | async fn connect(&mut self) -> Option<()> {
271 | for _ in 0..RETRIES {
272 | match self.state {
273 | State::Idle => {
274 | let buf = self.build_tcp_packet(tcp::TcpFlags::SYN, None);
275 | self.tun.send(&buf).await.unwrap();
276 | self.state = State::SynSent;
277 | info!("Sent SYN to server");
278 | }
279 | State::SynSent => {
280 | match time::timeout(TIMEOUT, self.incoming.recv_async()).await {
281 | Ok(buf) => {
282 | let buf = buf.unwrap();
283 | let (_v4_packet, tcp_packet) = parse_ip_packet(&buf).unwrap();
284 |
285 | if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
286 | return None;
287 | }
288 |
289 | if tcp_packet.get_flags() == tcp::TcpFlags::SYN | tcp::TcpFlags::ACK
290 | && tcp_packet.get_acknowledgement()
291 | == self.seq.load(Ordering::Relaxed) + 1
292 | {
293 | // found our SYN + ACK
294 | self.seq.fetch_add(1, Ordering::Relaxed);
295 | self.ack
296 | .store(tcp_packet.get_sequence() + 1, Ordering::Relaxed);
297 |
298 | // send ACK to finish handshake
299 | let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, None);
300 | self.tun.send(&buf).await.unwrap();
301 |
302 | self.state = State::Established;
303 |
304 | info!("Connection to {:?} established", self.remote_addr);
305 | return Some(());
306 | }
307 | }
308 | Err(_) => {
309 | info!("Waiting for SYN + ACK timed out");
310 | self.state = State::Idle;
311 | }
312 | }
313 | }
314 | _ => unreachable!(),
315 | }
316 | }
317 |
318 | None
319 | }
320 | }
321 |
322 | impl Drop for Socket {
323 | /// Drop the socket and close the TCP connection
324 | fn drop(&mut self) {
325 | let tuple = AddrTuple::new(self.local_addr, self.remote_addr);
326 | // dissociates ourself from the dispatch map
327 | assert!(self.shared.tuples.write().unwrap().remove(&tuple).is_some());
328 | // purge cache
329 | self.shared.tuples_purge.send(tuple).unwrap();
330 |
331 | let buf = build_tcp_packet(
332 | self.local_addr,
333 | self.remote_addr,
334 | self.seq.load(Ordering::Relaxed),
335 | 0,
336 | tcp::TcpFlags::RST,
337 | None,
338 | );
339 | if let Err(e) = self.tun.try_send(&buf) {
340 | warn!("Unable to send RST to remote end: {}", e);
341 | }
342 |
343 | info!("Fake TCP connection to {} closed", self);
344 | }
345 | }
346 |
347 | impl fmt::Display for Socket {
348 | /// User-friendly string representation of the socket
349 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 | write!(
351 | f,
352 | "(Fake TCP connection from {} to {})",
353 | self.local_addr, self.remote_addr
354 | )
355 | }
356 | }
357 |
358 | /// A userspace TCP state machine
359 | impl Stack {
360 | /// Create a new stack, `tun` is an array of [`Tun`](tokio_tun::Tun).
361 | /// When more than one [`Tun`](tokio_tun::Tun) object is passed in, same amount
362 | /// of reader will be spawned later. This allows user to utilize the performance
363 | /// benefit of Multiqueue Tun support on machines with SMP.
364 | pub fn new(tun: Vec, local_ip: Ipv4Addr, local_ip6: Option) -> Stack {
365 | let tun: Vec> = tun.into_iter().map(Arc::new).collect();
366 | let (ready_tx, ready_rx) = mpsc::channel(MPSC_BUFFER_LEN);
367 | let (tuples_purge_tx, _tuples_purge_rx) = broadcast::channel(16);
368 | let shared = Arc::new(Shared {
369 | tuples: RwLock::new(HashMap::new()),
370 | tun: tun.clone(),
371 | listening: RwLock::new(HashSet::new()),
372 | ready: ready_tx,
373 | tuples_purge: tuples_purge_tx.clone(),
374 | });
375 |
376 | for t in tun {
377 | tokio::spawn(Stack::reader_task(
378 | t,
379 | shared.clone(),
380 | tuples_purge_tx.subscribe(),
381 | ));
382 | }
383 |
384 | Stack {
385 | shared,
386 | local_ip,
387 | local_ip6,
388 | ready: ready_rx,
389 | }
390 | }
391 |
392 | /// Listens for incoming connections on the given `port`.
393 | pub fn listen(&mut self, port: u16) {
394 | assert!(self.shared.listening.write().unwrap().insert(port));
395 | }
396 |
397 | /// Accepts an incoming connection.
398 | pub async fn accept(&mut self) -> Socket {
399 | self.ready.recv().await.unwrap()
400 | }
401 |
402 | /// Connects to the remote end. `None` returned means
403 | /// the connection attempt failed.
404 | pub async fn connect(&mut self, addr: SocketAddr) -> Option {
405 | let mut rng = SmallRng::from_os_rng();
406 | for local_port in rng.random_range(32768..=60999)..=60999 {
407 | let local_addr = SocketAddr::new(
408 | if addr.is_ipv4() {
409 | IpAddr::V4(self.local_ip)
410 | } else {
411 | IpAddr::V6(self.local_ip6.expect("IPv6 local address undefined"))
412 | },
413 | local_port,
414 | );
415 | let tuple = AddrTuple::new(local_addr, addr);
416 | let mut sock;
417 |
418 | {
419 | let mut tuples = self.shared.tuples.write().unwrap();
420 | if tuples.contains_key(&tuple) {
421 | trace!(
422 | "Fake TCP connection to {}, local port number {} already in use, trying another one",
423 | addr, local_port
424 | );
425 | continue;
426 | }
427 |
428 | let incoming;
429 | (sock, incoming) = Socket::new(
430 | self.shared.clone(),
431 | self.shared.tun.choose(&mut rng).unwrap().clone(),
432 | local_addr,
433 | addr,
434 | None,
435 | State::Idle,
436 | );
437 |
438 | assert!(tuples.insert(tuple, incoming).is_none());
439 | }
440 |
441 | return sock.connect().await.map(|_| sock);
442 | }
443 |
444 | error!(
445 | "Fake TCP connection to {} failed, emphemeral port number exhausted",
446 | addr
447 | );
448 | None
449 | }
450 |
451 | async fn reader_task(
452 | tun: Arc,
453 | shared: Arc,
454 | mut tuples_purge: broadcast::Receiver,
455 | ) {
456 | let mut tuples: HashMap> = HashMap::new();
457 |
458 | loop {
459 | let mut buf = BytesMut::zeroed(MAX_PACKET_LEN);
460 |
461 | tokio::select! {
462 | size = tun.recv(&mut buf) => {
463 | let size = size.unwrap();
464 | buf.truncate(size);
465 | let buf = buf.freeze();
466 |
467 | match parse_ip_packet(&buf) {
468 | Some((ip_packet, tcp_packet)) => {
469 | let local_addr =
470 | SocketAddr::new(ip_packet.get_destination(), tcp_packet.get_destination());
471 | let remote_addr = SocketAddr::new(ip_packet.get_source(), tcp_packet.get_source());
472 |
473 | let tuple = AddrTuple::new(local_addr, remote_addr);
474 | if let Some(c) = tuples.get(&tuple) {
475 | if c.send_async(buf).await.is_err() {
476 | trace!("Cache hit, but receiver already closed, dropping packet");
477 | }
478 |
479 | continue;
480 |
481 | // If not Ok, receiver has been closed and just fall through to the slow
482 | // path below
483 | } else {
484 | trace!("Cache miss, checking the shared tuples table for connection");
485 | let sender = {
486 | let tuples = shared.tuples.read().unwrap();
487 | tuples.get(&tuple).cloned()
488 | };
489 |
490 | if let Some(c) = sender {
491 | trace!("Storing connection information into local tuples");
492 | tuples.insert(tuple, c.clone());
493 | c.send_async(buf).await.unwrap();
494 | continue;
495 | }
496 | }
497 |
498 | if tcp_packet.get_flags() == tcp::TcpFlags::SYN
499 | && shared
500 | .listening
501 | .read()
502 | .unwrap()
503 | .contains(&tcp_packet.get_destination())
504 | {
505 | // SYN seen on listening socket
506 | if tcp_packet.get_sequence() == 0 {
507 | let (sock, incoming) = Socket::new(
508 | shared.clone(),
509 | tun.clone(),
510 | local_addr,
511 | remote_addr,
512 | Some(tcp_packet.get_sequence() + 1),
513 | State::Idle,
514 | );
515 | assert!(shared
516 | .tuples
517 | .write()
518 | .unwrap()
519 | .insert(tuple, incoming)
520 | .is_none());
521 | tokio::spawn(sock.accept());
522 | } else {
523 | trace!("Bad TCP SYN packet from {}, sending RST", remote_addr);
524 | let buf = build_tcp_packet(
525 | local_addr,
526 | remote_addr,
527 | 0,
528 | tcp_packet.get_sequence() + tcp_packet.payload().len() as u32 + 1, // +1 because of SYN flag set
529 | tcp::TcpFlags::RST | tcp::TcpFlags::ACK,
530 | None,
531 | );
532 | shared.tun[0].try_send(&buf).unwrap();
533 | }
534 | } else if (tcp_packet.get_flags() & tcp::TcpFlags::RST) == 0 {
535 | info!("Unknown TCP packet from {}, sending RST", remote_addr);
536 | let buf = build_tcp_packet(
537 | local_addr,
538 | remote_addr,
539 | tcp_packet.get_acknowledgement(),
540 | tcp_packet.get_sequence() + tcp_packet.payload().len() as u32,
541 | tcp::TcpFlags::RST | tcp::TcpFlags::ACK,
542 | None,
543 | );
544 | shared.tun[0].try_send(&buf).unwrap();
545 | }
546 | }
547 | None => {
548 | continue;
549 | }
550 | }
551 | },
552 | tuple = tuples_purge.recv() => {
553 | let tuple = tuple.unwrap();
554 | tuples.remove(&tuple);
555 | trace!("Removed cached tuple: {:?}", tuple);
556 | }
557 | }
558 | }
559 | }
560 | }
561 |
--------------------------------------------------------------------------------
/fake-tcp/src/packet.rs:
--------------------------------------------------------------------------------
1 | use bytes::{Bytes, BytesMut};
2 | use internet_checksum::Checksum;
3 | use pnet::packet::Packet;
4 | use pnet::packet::{ip, ipv4, ipv6, tcp};
5 | use std::convert::TryInto;
6 | use std::net::{IpAddr, SocketAddr};
7 |
8 | const IPV4_HEADER_LEN: usize = 20;
9 | const IPV6_HEADER_LEN: usize = 40;
10 | const TCP_HEADER_LEN: usize = 20;
11 | pub const MAX_PACKET_LEN: usize = 1500;
12 |
13 | pub enum IPPacket<'p> {
14 | V4(ipv4::Ipv4Packet<'p>),
15 | V6(ipv6::Ipv6Packet<'p>),
16 | }
17 |
18 | impl IPPacket<'_> {
19 | pub fn get_source(&self) -> IpAddr {
20 | match self {
21 | IPPacket::V4(p) => IpAddr::V4(p.get_source()),
22 | IPPacket::V6(p) => IpAddr::V6(p.get_source()),
23 | }
24 | }
25 |
26 | pub fn get_destination(&self) -> IpAddr {
27 | match self {
28 | IPPacket::V4(p) => IpAddr::V4(p.get_destination()),
29 | IPPacket::V6(p) => IpAddr::V6(p.get_destination()),
30 | }
31 | }
32 | }
33 |
34 | pub fn build_tcp_packet(
35 | local_addr: SocketAddr,
36 | remote_addr: SocketAddr,
37 | seq: u32,
38 | ack: u32,
39 | flags: u8,
40 | payload: Option<&[u8]>,
41 | ) -> Bytes {
42 | let ip_header_len = match local_addr {
43 | SocketAddr::V4(_) => IPV4_HEADER_LEN,
44 | SocketAddr::V6(_) => IPV6_HEADER_LEN,
45 | };
46 | let wscale = (flags & tcp::TcpFlags::SYN) != 0;
47 | let tcp_header_len = TCP_HEADER_LEN + if wscale { 4 } else { 0 }; // nop + wscale
48 | let tcp_total_len = tcp_header_len + payload.map_or(0, |payload| payload.len());
49 | let total_len = ip_header_len + tcp_total_len;
50 | let mut buf = BytesMut::zeroed(total_len);
51 |
52 | let mut ip_buf = buf.split_to(ip_header_len);
53 | let mut tcp_buf = buf.split_to(tcp_total_len);
54 | assert_eq!(0, buf.len());
55 |
56 | match (local_addr, remote_addr) {
57 | (SocketAddr::V4(local), SocketAddr::V4(remote)) => {
58 | let mut v4 = ipv4::MutableIpv4Packet::new(&mut ip_buf).unwrap();
59 | v4.set_version(4);
60 | v4.set_header_length(IPV4_HEADER_LEN as u8 / 4);
61 | v4.set_next_level_protocol(ip::IpNextHeaderProtocols::Tcp);
62 | v4.set_ttl(64);
63 | v4.set_source(*local.ip());
64 | v4.set_destination(*remote.ip());
65 | v4.set_total_length(total_len.try_into().unwrap());
66 | v4.set_flags(ipv4::Ipv4Flags::DontFragment);
67 | let mut cksm = Checksum::new();
68 | cksm.add_bytes(v4.packet());
69 | v4.set_checksum(u16::from_be_bytes(cksm.checksum()));
70 | }
71 | (SocketAddr::V6(local), SocketAddr::V6(remote)) => {
72 | let mut v6 = ipv6::MutableIpv6Packet::new(&mut ip_buf).unwrap();
73 | v6.set_version(6);
74 | v6.set_payload_length(tcp_total_len.try_into().unwrap());
75 | v6.set_next_header(ip::IpNextHeaderProtocols::Tcp);
76 | v6.set_hop_limit(64);
77 | v6.set_source(*local.ip());
78 | v6.set_destination(*remote.ip());
79 | }
80 | _ => unreachable!(),
81 | };
82 |
83 | let mut tcp = tcp::MutableTcpPacket::new(&mut tcp_buf).unwrap();
84 | tcp.set_window(0xffff);
85 | tcp.set_source(local_addr.port());
86 | tcp.set_destination(remote_addr.port());
87 | tcp.set_sequence(seq);
88 | tcp.set_acknowledgement(ack);
89 | tcp.set_flags(flags);
90 | tcp.set_data_offset(TCP_HEADER_LEN as u8 / 4 + if wscale { 1 } else { 0 });
91 | if wscale {
92 | let wscale = tcp::TcpOption::wscale(14);
93 | tcp.set_options(&[tcp::TcpOption::nop(), wscale]);
94 | }
95 |
96 | if let Some(payload) = payload {
97 | tcp.set_payload(payload);
98 | }
99 |
100 | let mut cksm = Checksum::new();
101 | let ip::IpNextHeaderProtocol(tcp_protocol) = ip::IpNextHeaderProtocols::Tcp;
102 |
103 | match (local_addr, remote_addr) {
104 | (SocketAddr::V4(local), SocketAddr::V4(remote)) => {
105 | cksm.add_bytes(&local.ip().octets());
106 | cksm.add_bytes(&remote.ip().octets());
107 |
108 | let mut pseudo = [0u8, tcp_protocol, 0, 0];
109 | pseudo[2..].copy_from_slice(&(tcp_total_len as u16).to_be_bytes());
110 | cksm.add_bytes(&pseudo);
111 | }
112 | (SocketAddr::V6(local), SocketAddr::V6(remote)) => {
113 | cksm.add_bytes(&local.ip().octets());
114 | cksm.add_bytes(&remote.ip().octets());
115 |
116 | let mut pseudo = [0u8, 0, 0, 0, 0, 0, 0, tcp_protocol];
117 | pseudo[0..4].copy_from_slice(&(tcp_total_len as u32).to_be_bytes());
118 | cksm.add_bytes(&pseudo);
119 | }
120 | _ => unreachable!(),
121 | };
122 |
123 | cksm.add_bytes(tcp.packet());
124 | tcp.set_checksum(u16::from_be_bytes(cksm.checksum()));
125 |
126 | ip_buf.unsplit(tcp_buf);
127 | ip_buf.freeze()
128 | }
129 |
130 | pub fn parse_ip_packet(buf: &Bytes) -> Option<(IPPacket, tcp::TcpPacket)> {
131 | if buf[0] >> 4 == 4 {
132 | let v4 = ipv4::Ipv4Packet::new(buf).unwrap();
133 | if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp {
134 | return None;
135 | }
136 |
137 | let tcp = tcp::TcpPacket::new(&buf[IPV4_HEADER_LEN..]).unwrap();
138 | Some((IPPacket::V4(v4), tcp))
139 | } else if buf[0] >> 4 == 6 {
140 | let v6 = ipv6::Ipv6Packet::new(buf).unwrap();
141 | if v6.get_next_header() != ip::IpNextHeaderProtocols::Tcp {
142 | return None;
143 | }
144 |
145 | let tcp = tcp::TcpPacket::new(&buf[IPV6_HEADER_LEN..]).unwrap();
146 | Some((IPPacket::V6(v6), tcp))
147 | } else {
148 | None
149 | }
150 | }
151 |
152 | #[cfg(all(test, feature = "benchmark"))]
153 | mod benchmarks {
154 | extern crate test;
155 | use super::*;
156 | use test::{black_box, Bencher};
157 |
158 | #[bench]
159 | fn bench_build_tcp_packet_1460(b: &mut Bencher) {
160 | let local_addr = "127.0.0.1:1234".parse().unwrap();
161 | let remote_addr = "127.0.0.2:1234".parse().unwrap();
162 | let payload = black_box([123u8; 1460]);
163 | b.iter(|| {
164 | build_tcp_packet(
165 | local_addr,
166 | remote_addr,
167 | 123,
168 | 456,
169 | tcp::TcpFlags::ACK,
170 | Some(&payload),
171 | )
172 | });
173 | }
174 |
175 | #[bench]
176 | fn bench_build_tcp_packet_512(b: &mut Bencher) {
177 | let local_addr = "127.0.0.1:1234".parse().unwrap();
178 | let remote_addr = "127.0.0.2:1234".parse().unwrap();
179 | let payload = black_box([123u8; 512]);
180 | b.iter(|| {
181 | build_tcp_packet(
182 | local_addr,
183 | remote_addr,
184 | 123,
185 | 456,
186 | tcp::TcpFlags::ACK,
187 | Some(&payload),
188 | )
189 | });
190 | }
191 |
192 | #[bench]
193 | fn bench_build_tcp_packet_128(b: &mut Bencher) {
194 | let local_addr = "127.0.0.1:1234".parse().unwrap();
195 | let remote_addr = "127.0.0.2:1234".parse().unwrap();
196 | let payload = black_box([123u8; 128]);
197 | b.iter(|| {
198 | build_tcp_packet(
199 | local_addr,
200 | remote_addr,
201 | 123,
202 | 456,
203 | tcp::TcpFlags::ACK,
204 | Some(&payload),
205 | )
206 | });
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/images/packet-headers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dndx/phantun/869c79422f1126a9994b756a8ffc7a9405e4946f/images/packet-headers.png
--------------------------------------------------------------------------------
/images/phantun-vs-udp2raw-benchmark-result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dndx/phantun/869c79422f1126a9994b756a8ffc7a9405e4946f/images/phantun-vs-udp2raw-benchmark-result.png
--------------------------------------------------------------------------------
/images/traffic-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dndx/phantun/869c79422f1126a9994b756a8ffc7a9405e4946f/images/traffic-flow.png
--------------------------------------------------------------------------------
/phantun/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "phantun"
3 | version = "0.7.0"
4 | edition = "2021"
5 | authors = ["Datong Sun "]
6 | license = "MIT OR Apache-2.0"
7 | repository = "https://github.com/dndx/phantun"
8 | readme = "README.md"
9 | description = """
10 | Transforms UDP stream into (fake) TCP streams that can go through
11 | Layer 3 & Layer 4 (NAPT) firewalls/NATs.
12 | """
13 | [dependencies]
14 | clap = { version = "4", features = ["cargo"] }
15 | socket2 = { version = "0", features = ["all"] }
16 | fake-tcp = { path = "../fake-tcp", version = "0" }
17 | tokio = { version = "1", features = ["full"] }
18 | tokio-util = "0"
19 | log = "0"
20 | pretty_env_logger = "0"
21 | tokio-tun = "0"
22 | num_cpus = "1"
23 | neli = "0"
24 | nix = { version = "0", features = ["net"] }
25 |
--------------------------------------------------------------------------------
/phantun/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2021-2024 Datong Sun (dndx@idndx.com)
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/phantun/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-2024 Datong Sun (dndx@idndx.com)
4 |
5 | Permission is hereby granted, free of charge, to any
6 | person obtaining a copy of this software and associated
7 | documentation files (the "Software"), to deal in the
8 | Software without restriction, including without
9 | limitation the rights to use, copy, modify, merge,
10 | publish, distribute, sublicense, and/or sell copies of
11 | the Software, and to permit persons to whom the Software
12 | is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice
16 | shall be included in all copies or substantial portions
17 | of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 | DEALINGS IN THE SOFTWARE.
28 |
--------------------------------------------------------------------------------
/phantun/README.md:
--------------------------------------------------------------------------------
1 | # Phantun
2 |
3 | Client/Server crate, see [Phantun Project README.md](https://github.com/dndx/phantun/blob/main/README.md) for more information.
4 |
5 | ## License
6 |
7 | Copyright 2021 Datong Sun
8 |
9 | Licensed under the Apache License, Version 2.0 or the MIT license
11 | , at your
12 | option. Files in the project may not be
13 | copied, modified, or distributed except according to those terms.
14 |
--------------------------------------------------------------------------------
/phantun/src/bin/client.rs:
--------------------------------------------------------------------------------
1 | use clap::{crate_version, Arg, ArgAction, Command};
2 | use fake_tcp::packet::MAX_PACKET_LEN;
3 | use fake_tcp::{Socket, Stack};
4 | use log::{debug, error, info};
5 | use phantun::utils::{assign_ipv6_address, new_udp_reuseport};
6 | use std::collections::HashMap;
7 | use std::fs;
8 | use std::io;
9 | use std::net::{Ipv4Addr, SocketAddr};
10 | use std::sync::Arc;
11 | use tokio::sync::{Notify, RwLock};
12 | use tokio::time;
13 | use tokio_tun::TunBuilder;
14 | use tokio_util::sync::CancellationToken;
15 |
16 | use phantun::UDP_TTL;
17 |
18 | #[tokio::main]
19 | async fn main() -> io::Result<()> {
20 | pretty_env_logger::init();
21 |
22 | let matches = Command::new("Phantun Client")
23 | .version(crate_version!())
24 | .author("Datong Sun (github.com/dndx)")
25 | .arg(
26 | Arg::new("local")
27 | .short('l')
28 | .long("local")
29 | .required(true)
30 | .value_name("IP:PORT")
31 | .help("Sets the IP and port where Phantun Client listens for incoming UDP datagrams, IPv6 address need to be specified as: \"[IPv6]:PORT\"")
32 | )
33 | .arg(
34 | Arg::new("remote")
35 | .short('r')
36 | .long("remote")
37 | .required(true)
38 | .value_name("IP or HOST NAME:PORT")
39 | .help("Sets the address or host name and port where Phantun Client connects to Phantun Server, IPv6 address need to be specified as: \"[IPv6]:PORT\"")
40 | )
41 | .arg(
42 | Arg::new("tun")
43 | .long("tun")
44 | .required(false)
45 | .value_name("tunX")
46 | .help("Sets the Tun interface name, if absent, pick the next available name")
47 | .default_value("")
48 | )
49 | .arg(
50 | Arg::new("tun_local")
51 | .long("tun-local")
52 | .required(false)
53 | .value_name("IP")
54 | .help("Sets the Tun interface IPv4 local address (O/S's end)")
55 | .default_value("192.168.200.1")
56 | )
57 | .arg(
58 | Arg::new("tun_peer")
59 | .long("tun-peer")
60 | .required(false)
61 | .value_name("IP")
62 | .help("Sets the Tun interface IPv4 destination (peer) address (Phantun Client's end). \
63 | You will need to setup SNAT/MASQUERADE rules on your Internet facing interface \
64 | in order for Phantun Client to connect to Phantun Server")
65 | .default_value("192.168.200.2")
66 | )
67 | .arg(
68 | Arg::new("ipv4_only")
69 | .long("ipv4-only")
70 | .short('4')
71 | .required(false)
72 | .help("Only use IPv4 address when connecting to remote")
73 | .action(ArgAction::SetTrue)
74 | .conflicts_with_all(["tun_local6", "tun_peer6"]),
75 | )
76 | .arg(
77 | Arg::new("tun_local6")
78 | .long("tun-local6")
79 | .required(false)
80 | .value_name("IP")
81 | .help("Sets the Tun interface IPv6 local address (O/S's end)")
82 | .default_value("fcc8::1")
83 | )
84 | .arg(
85 | Arg::new("tun_peer6")
86 | .long("tun-peer6")
87 | .required(false)
88 | .value_name("IP")
89 | .help("Sets the Tun interface IPv6 destination (peer) address (Phantun Client's end). \
90 | You will need to setup SNAT/MASQUERADE rules on your Internet facing interface \
91 | in order for Phantun Client to connect to Phantun Server")
92 | .default_value("fcc8::2")
93 | )
94 | .arg(
95 | Arg::new("handshake_packet")
96 | .long("handshake-packet")
97 | .required(false)
98 | .value_name("PATH")
99 | .help("Specify a file, which, after TCP handshake, its content will be sent as the \
100 | first data packet to the server.\n\
101 | Note: ensure this file's size does not exceed the MTU of the outgoing interface. \
102 | The content is always sent out in a single packet and will not be further segmented")
103 | )
104 | .get_matches();
105 |
106 | let local_addr: SocketAddr = matches
107 | .get_one::("local")
108 | .unwrap()
109 | .parse()
110 | .expect("bad local address");
111 |
112 | let ipv4_only = matches.get_flag("ipv4_only");
113 |
114 | let remote_addr = tokio::net::lookup_host(matches.get_one::("remote").unwrap())
115 | .await
116 | .expect("bad remote address or host")
117 | .find(|addr| !ipv4_only || addr.is_ipv4())
118 | .expect("unable to resolve remote host name");
119 | info!("Remote address is: {}", remote_addr);
120 |
121 | let tun_local: Ipv4Addr = matches
122 | .get_one::("tun_local")
123 | .unwrap()
124 | .parse()
125 | .expect("bad local address for Tun interface");
126 | let tun_peer: Ipv4Addr = matches
127 | .get_one::("tun_peer")
128 | .unwrap()
129 | .parse()
130 | .expect("bad peer address for Tun interface");
131 |
132 | let (tun_local6, tun_peer6) = if matches.get_flag("ipv4_only") {
133 | (None, None)
134 | } else {
135 | (
136 | matches
137 | .get_one::("tun_local6")
138 | .map(|v| v.parse().expect("bad local address for Tun interface")),
139 | matches
140 | .get_one::("tun_peer6")
141 | .map(|v| v.parse().expect("bad peer address for Tun interface")),
142 | )
143 | };
144 |
145 | let tun_name = matches.get_one::("tun").unwrap();
146 | let handshake_packet: Option> = matches
147 | .get_one::("handshake_packet")
148 | .map(fs::read)
149 | .transpose()?;
150 |
151 | let num_cpus = num_cpus::get();
152 | info!("{} cores available", num_cpus);
153 |
154 | let tun = TunBuilder::new()
155 | .name(tun_name) // if name is empty, then it is set by kernel.
156 | .up() // or set it up manually using `sudo ip link set up`.
157 | .address(tun_local)
158 | .destination(tun_peer)
159 | .queues(num_cpus)
160 | .build()
161 | .unwrap();
162 |
163 | if remote_addr.is_ipv6() {
164 | assign_ipv6_address(tun[0].name(), tun_local6.unwrap(), tun_peer6.unwrap());
165 | }
166 |
167 | info!("Created TUN device {}", tun[0].name());
168 |
169 | let udp_sock = Arc::new(new_udp_reuseport(local_addr));
170 | let connections = Arc::new(RwLock::new(HashMap::>::new()));
171 |
172 | let mut stack = Stack::new(tun, tun_peer, tun_peer6);
173 |
174 | let main_loop = tokio::spawn(async move {
175 | let mut buf_r = [0u8; MAX_PACKET_LEN];
176 |
177 | loop {
178 | let (size, addr) = udp_sock.recv_from(&mut buf_r).await?;
179 | // seen UDP packet to listening socket, this means:
180 | // 1. It is a new UDP connection, or
181 | // 2. It is some extra packets not filtered by more specific
182 | // connected UDP socket yet
183 | if let Some(sock) = connections.read().await.get(&addr) {
184 | sock.send(&buf_r[..size]).await;
185 | continue;
186 | }
187 |
188 | info!("New UDP client from {}", addr);
189 | let sock = stack.connect(remote_addr).await;
190 | if sock.is_none() {
191 | error!("Unable to connect to remote {}", remote_addr);
192 | continue;
193 | }
194 |
195 | let sock = Arc::new(sock.unwrap());
196 | if let Some(ref p) = handshake_packet {
197 | if sock.send(p).await.is_none() {
198 | error!("Failed to send handshake packet to remote, closing connection.");
199 | continue;
200 | }
201 |
202 | debug!("Sent handshake packet to: {}", sock);
203 | }
204 |
205 | // send first packet
206 | if sock.send(&buf_r[..size]).await.is_none() {
207 | continue;
208 | }
209 |
210 | assert!(connections
211 | .write()
212 | .await
213 | .insert(addr, sock.clone())
214 | .is_none());
215 | debug!("inserted fake TCP socket into connection table");
216 |
217 | // spawn "fastpath" UDP socket and task, this will offload main task
218 | // from forwarding UDP packets
219 |
220 | let packet_received = Arc::new(Notify::new());
221 | let quit = CancellationToken::new();
222 |
223 | for i in 0..num_cpus {
224 | let sock = sock.clone();
225 | let quit = quit.clone();
226 | let packet_received = packet_received.clone();
227 |
228 | tokio::spawn(async move {
229 | let mut buf_udp = [0u8; MAX_PACKET_LEN];
230 | let mut buf_tcp = [0u8; MAX_PACKET_LEN];
231 | let udp_sock = new_udp_reuseport(local_addr);
232 | udp_sock.connect(addr).await.unwrap();
233 |
234 | loop {
235 | tokio::select! {
236 | Ok(size) = udp_sock.recv(&mut buf_udp) => {
237 | if sock.send(&buf_udp[..size]).await.is_none() {
238 | debug!("removed fake TCP socket from connections table");
239 | quit.cancel();
240 | return;
241 | }
242 |
243 | packet_received.notify_one();
244 | },
245 | res = sock.recv(&mut buf_tcp) => {
246 | match res {
247 | Some(size) => {
248 | if size > 0 {
249 | if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
250 | error!("Unable to send UDP packet to {}: {}, closing connection", e, addr);
251 | quit.cancel();
252 | return;
253 | }
254 | }
255 | },
256 | None => {
257 | debug!("removed fake TCP socket from connections table");
258 | quit.cancel();
259 | return;
260 | },
261 | }
262 |
263 | packet_received.notify_one();
264 | },
265 | _ = quit.cancelled() => {
266 | debug!("worker {} terminated", i);
267 | return;
268 | },
269 | };
270 | }
271 | });
272 | }
273 |
274 | let connections = connections.clone();
275 | tokio::spawn(async move {
276 | loop {
277 | let read_timeout = time::sleep(UDP_TTL);
278 | let packet_received_fut = packet_received.notified();
279 |
280 | tokio::select! {
281 | _ = read_timeout => {
282 | info!("No traffic seen in the last {:?}, closing connection", UDP_TTL);
283 | connections.write().await.remove(&addr);
284 | debug!("removed fake TCP socket from connections table");
285 |
286 | quit.cancel();
287 | return;
288 | },
289 | _ = quit.cancelled() => {
290 | connections.write().await.remove(&addr);
291 | debug!("removed fake TCP socket from connections table");
292 | return;
293 | },
294 | _ = packet_received_fut => {},
295 | }
296 | }
297 | });
298 | }
299 | });
300 |
301 | tokio::join!(main_loop).0.unwrap()
302 | }
303 |
--------------------------------------------------------------------------------
/phantun/src/bin/server.rs:
--------------------------------------------------------------------------------
1 | use clap::{crate_version, Arg, ArgAction, Command};
2 | use fake_tcp::packet::MAX_PACKET_LEN;
3 | use fake_tcp::Stack;
4 | use log::{debug, error, info};
5 | use phantun::utils::{assign_ipv6_address, new_udp_reuseport};
6 | use std::fs;
7 | use std::io;
8 | use std::net::Ipv4Addr;
9 | use std::sync::Arc;
10 | use tokio::net::UdpSocket;
11 | use tokio::sync::Notify;
12 | use tokio::time;
13 | use tokio_tun::TunBuilder;
14 | use tokio_util::sync::CancellationToken;
15 |
16 | use phantun::UDP_TTL;
17 |
18 | #[tokio::main]
19 | async fn main() -> io::Result<()> {
20 | pretty_env_logger::init();
21 |
22 | let matches = Command::new("Phantun Server")
23 | .version(crate_version!())
24 | .author("Datong Sun (github.com/dndx)")
25 | .arg(
26 | Arg::new("local")
27 | .short('l')
28 | .long("local")
29 | .required(true)
30 | .value_name("PORT")
31 | .help("Sets the port where Phantun Server listens for incoming Phantun Client TCP connections")
32 | )
33 | .arg(
34 | Arg::new("remote")
35 | .short('r')
36 | .long("remote")
37 | .required(true)
38 | .value_name("IP or HOST NAME:PORT")
39 | .help("Sets the address or host name and port where Phantun Server forwards UDP packets to, IPv6 address need to be specified as: \"[IPv6]:PORT\"")
40 | )
41 | .arg(
42 | Arg::new("tun")
43 | .long("tun")
44 | .required(false)
45 | .value_name("tunX")
46 | .help("Sets the Tun interface name, if absent, pick the next available name")
47 | .default_value("")
48 | )
49 | .arg(
50 | Arg::new("tun_local")
51 | .long("tun-local")
52 | .required(false)
53 | .value_name("IP")
54 | .help("Sets the Tun interface local address (O/S's end)")
55 | .default_value("192.168.201.1")
56 | )
57 | .arg(
58 | Arg::new("tun_peer")
59 | .long("tun-peer")
60 | .required(false)
61 | .value_name("IP")
62 | .help("Sets the Tun interface destination (peer) address (Phantun Server's end). \
63 | You will need to setup DNAT rules to this address in order for Phantun Server \
64 | to accept TCP traffic from Phantun Client")
65 | .default_value("192.168.201.2")
66 | )
67 | .arg(
68 | Arg::new("ipv4_only")
69 | .long("ipv4-only")
70 | .short('4')
71 | .required(false)
72 | .help("Do not assign IPv6 addresses to Tun interface")
73 | .action(ArgAction::SetTrue)
74 | .conflicts_with_all(["tun_local6", "tun_peer6"]),
75 | )
76 | .arg(
77 | Arg::new("tun_local6")
78 | .long("tun-local6")
79 | .required(false)
80 | .value_name("IP")
81 | .help("Sets the Tun interface IPv6 local address (O/S's end)")
82 | .default_value("fcc9::1")
83 | )
84 | .arg(
85 | Arg::new("tun_peer6")
86 | .long("tun-peer6")
87 | .required(false)
88 | .value_name("IP")
89 | .help("Sets the Tun interface IPv6 destination (peer) address (Phantun Client's end). \
90 | You will need to setup SNAT/MASQUERADE rules on your Internet facing interface \
91 | in order for Phantun Client to connect to Phantun Server")
92 | .default_value("fcc9::2")
93 | )
94 | .arg(
95 | Arg::new("handshake_packet")
96 | .long("handshake-packet")
97 | .required(false)
98 | .value_name("PATH")
99 | .help("Specify a file, which, after TCP handshake, its content will be sent as the \
100 | first data packet to the client.\n\
101 | Note: ensure this file's size does not exceed the MTU of the outgoing interface. \
102 | The content is always sent out in a single packet and will not be further segmented")
103 | )
104 | .get_matches();
105 |
106 | let local_port: u16 = matches
107 | .get_one::("local")
108 | .unwrap()
109 | .parse()
110 | .expect("bad local port");
111 |
112 | let remote_addr = tokio::net::lookup_host(matches.get_one::("remote").unwrap())
113 | .await
114 | .expect("bad remote address or host")
115 | .next()
116 | .expect("unable to resolve remote host name");
117 |
118 | info!("Remote address is: {}", remote_addr);
119 |
120 | let tun_local: Ipv4Addr = matches
121 | .get_one::("tun_local")
122 | .unwrap()
123 | .parse()
124 | .expect("bad local address for Tun interface");
125 | let tun_peer: Ipv4Addr = matches
126 | .get_one::("tun_peer")
127 | .unwrap()
128 | .parse()
129 | .expect("bad peer address for Tun interface");
130 |
131 | let (tun_local6, tun_peer6) = if matches.get_flag("ipv4_only") {
132 | (None, None)
133 | } else {
134 | (
135 | matches
136 | .get_one::("tun_local6")
137 | .map(|v| v.parse().expect("bad local address for Tun interface")),
138 | matches
139 | .get_one::("tun_peer6")
140 | .map(|v| v.parse().expect("bad peer address for Tun interface")),
141 | )
142 | };
143 |
144 | let tun_name = matches.get_one::("tun").unwrap();
145 | let handshake_packet: Option> = matches
146 | .get_one::("handshake_packet")
147 | .map(fs::read)
148 | .transpose()?;
149 |
150 | let num_cpus = num_cpus::get();
151 | info!("{} cores available", num_cpus);
152 |
153 | let tun = TunBuilder::new()
154 | .name(tun_name) // if name is empty, then it is set by kernel.
155 | .up() // or set it up manually using `sudo ip link set up`.
156 | .address(tun_local)
157 | .destination(tun_peer)
158 | .queues(num_cpus)
159 | .build()
160 | .unwrap();
161 |
162 | if let (Some(tun_local6), Some(tun_peer6)) = (tun_local6, tun_peer6) {
163 | assign_ipv6_address(tun[0].name(), tun_local6, tun_peer6);
164 | }
165 |
166 | info!("Created TUN device {}", tun[0].name());
167 |
168 | //thread::sleep(time::Duration::from_secs(5));
169 | let mut stack = Stack::new(tun, tun_local, tun_local6);
170 | stack.listen(local_port);
171 | info!("Listening on {}", local_port);
172 |
173 | let main_loop = tokio::spawn(async move {
174 | let mut buf_udp = [0u8; MAX_PACKET_LEN];
175 | let mut buf_tcp = [0u8; MAX_PACKET_LEN];
176 |
177 | loop {
178 | let sock = Arc::new(stack.accept().await);
179 | info!("New connection: {}", sock);
180 | if let Some(ref p) = handshake_packet {
181 | if sock.send(p).await.is_none() {
182 | error!("Failed to send handshake packet to remote, closing connection.");
183 | continue;
184 | }
185 |
186 | debug!("Sent handshake packet to: {}", sock);
187 | }
188 |
189 | let packet_received = Arc::new(Notify::new());
190 | let quit = CancellationToken::new();
191 | let udp_sock = UdpSocket::bind(if remote_addr.is_ipv4() {
192 | "0.0.0.0:0"
193 | } else {
194 | "[::]:0"
195 | })
196 | .await?;
197 | let local_addr = udp_sock.local_addr()?;
198 | drop(udp_sock);
199 |
200 | for i in 0..num_cpus {
201 | let sock = sock.clone();
202 | let quit = quit.clone();
203 | let packet_received = packet_received.clone();
204 | let udp_sock = new_udp_reuseport(local_addr);
205 |
206 | tokio::spawn(async move {
207 | udp_sock.connect(remote_addr).await.unwrap();
208 |
209 | loop {
210 | tokio::select! {
211 | Ok(size) = udp_sock.recv(&mut buf_udp) => {
212 | if sock.send(&buf_udp[..size]).await.is_none() {
213 | quit.cancel();
214 | return;
215 | }
216 |
217 | packet_received.notify_one();
218 | },
219 | res = sock.recv(&mut buf_tcp) => {
220 | match res {
221 | Some(size) => {
222 | if size > 0 {
223 | if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
224 | error!("Unable to send UDP packet to {}: {}, closing connection", e, remote_addr);
225 | quit.cancel();
226 | return;
227 | }
228 | }
229 | },
230 | None => {
231 | quit.cancel();
232 | return;
233 | },
234 | }
235 |
236 | packet_received.notify_one();
237 | },
238 | _ = quit.cancelled() => {
239 | debug!("worker {} terminated", i);
240 | return;
241 | },
242 | };
243 | }
244 | });
245 | }
246 |
247 | tokio::spawn(async move {
248 | loop {
249 | let read_timeout = time::sleep(UDP_TTL);
250 | let packet_received_fut = packet_received.notified();
251 |
252 | tokio::select! {
253 | _ = read_timeout => {
254 | info!("No traffic seen in the last {:?}, closing connection", UDP_TTL);
255 |
256 | quit.cancel();
257 | return;
258 | },
259 | _ = packet_received_fut => {},
260 | }
261 | }
262 | });
263 | }
264 | });
265 |
266 | tokio::join!(main_loop).0.unwrap()
267 | }
268 |
--------------------------------------------------------------------------------
/phantun/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 |
3 | pub mod utils;
4 |
5 | pub const UDP_TTL: Duration = Duration::from_secs(180);
6 |
--------------------------------------------------------------------------------
/phantun/src/utils.rs:
--------------------------------------------------------------------------------
1 | use neli::{
2 | consts::{
3 | nl::{NlmF, NlmFFlags},
4 | rtnl::{Ifa, IfaFFlags, RtAddrFamily, Rtm},
5 | socket::NlFamily,
6 | },
7 | nl::{NlPayload, Nlmsghdr},
8 | rtnl::{Ifaddrmsg, Rtattr},
9 | socket::NlSocketHandle,
10 | types::RtBuffer,
11 | };
12 | use std::net::{Ipv6Addr, SocketAddr};
13 | use tokio::net::UdpSocket;
14 |
15 | pub fn new_udp_reuseport(local_addr: SocketAddr) -> UdpSocket {
16 | let udp_sock = socket2::Socket::new(
17 | if local_addr.is_ipv4() {
18 | socket2::Domain::IPV4
19 | } else {
20 | socket2::Domain::IPV6
21 | },
22 | socket2::Type::DGRAM,
23 | None,
24 | )
25 | .unwrap();
26 | udp_sock.set_reuse_port(true).unwrap();
27 | // from tokio-rs/mio/blob/master/src/sys/unix/net.rs
28 | udp_sock.set_cloexec(true).unwrap();
29 | udp_sock.set_nonblocking(true).unwrap();
30 | udp_sock.bind(&socket2::SockAddr::from(local_addr)).unwrap();
31 | let udp_sock: std::net::UdpSocket = udp_sock.into();
32 | udp_sock.try_into().unwrap()
33 | }
34 |
35 | pub fn assign_ipv6_address(device_name: &str, local: Ipv6Addr, peer: Ipv6Addr) {
36 | let index = nix::net::if_::if_nametoindex(device_name).unwrap();
37 |
38 | let mut rtnl = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap();
39 | let mut rtattrs = RtBuffer::new();
40 | rtattrs.push(Rtattr::new(None, Ifa::Local, &local.octets()[..]).unwrap());
41 | rtattrs.push(Rtattr::new(None, Ifa::Address, &peer.octets()[..]).unwrap());
42 |
43 | let ifaddrmsg = Ifaddrmsg {
44 | ifa_family: RtAddrFamily::Inet6,
45 | ifa_prefixlen: 128,
46 | ifa_flags: IfaFFlags::empty(),
47 | ifa_scope: 0,
48 | ifa_index: index as i32,
49 | rtattrs,
50 | };
51 | let nl_header = Nlmsghdr::new(
52 | None,
53 | Rtm::Newaddr,
54 | NlmFFlags::new(&[NlmF::Request]),
55 | None,
56 | None,
57 | NlPayload::Payload(ifaddrmsg),
58 | );
59 | rtnl.send(nl_header).unwrap();
60 | }
61 |
--------------------------------------------------------------------------------