35 Commits

Author SHA1 Message Date
Datong Sun
b8a6c8853b chore(phantun) bump to v0.3.0 2022-04-09 08:39:44 -07:00
Datong Sun
d97a27778b style(phantun) refactor out common functions and constants 2022-04-09 21:32:07 +08:00
Datong Sun
35f7b35ff5 perf(phantun) spawn multiple threads for UDP send/receive 2022-04-09 21:32:07 +08:00
Datong Sun
dff0c4ca28 docs(readme) add link for fake-tcp docs 2022-04-09 12:17:11 +08:00
Datong Sun
9bf78adc92 chore(fake-tcp) bump to v0.2.4 with new documentations 2022-04-08 21:10:36 -07:00
Datong Sun
5d4e3bf8c0 docs(fake-tcp) added documentations for fake-tcp 2022-04-09 12:10:13 +08:00
Datong Sun
9c85b43e94 style(phantun) use the clap::Command struct, removed the deprecated clap::App usage 2022-04-09 11:00:20 +08:00
Datong Sun
66b0bc11b0 chore(phantun) use path dependency for fake-tcp crate 2022-04-09 11:00:20 +08:00
Datong Sun
02b00dfc3a docs(images) updated the flow diagram 2022-03-22 05:16:31 -07:00
dependabot[bot]
0ee7774d03 chore(deps): bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-03 10:40:48 +08:00
dependabot[bot]
11fdac78f1 chore(deps): update pnet requirement from 0.28 to 0.29
Updates the requirements on [pnet](https://github.com/libpnet/libpnet) to permit the latest version.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: pnet
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-14 23:44:23 +08:00
Datong Sun
ed686ce9fa docs(licenses) updated to year 2022 2022-01-03 07:57:09 -08:00
Datong Sun
d9001b08aa docs(readme) bumped latest release to v0.2.5 2022-01-03 07:54:41 -08:00
Datong Sun
726ecac9cf chore(phantun) bump phantun to v0.2.5 2022-01-03 07:47:48 -08:00
dependabot[bot]
2ef0a056be chore(deps): update clap requirement from 2.34 to 3.0
Updates the requirements on [clap](https://github.com/clap-rs/clap) to permit the latest version.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_generate-v3.0.0-rc.0...clap_complete-v3.0.0)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 23:45:13 +08:00
Datong Sun
cb9dd3e931 fix(client) disable AAAA resolve, since tokio-tun does not yet have IPv6
support. See: https://github.com/yaa110/tokio-tun/pull/8
2022-01-03 23:37:31 +08:00
Datong Sun
7db7164193 chore(*) use tokio-tun v0.5 instead of forked version. Bumped
`fake-tcp` to `v0.2.3`
2021-12-07 17:07:54 +08:00
Datong Sun
def134d73b docs(readme) bump latest version to v0.2.4 2021-12-05 07:13:01 -08:00
Datong Sun
b3c781cdc5 chore(phantun) bump phantun to v0.2.4 2021-12-05 07:10:42 -08:00
Datong Sun
d5e30c113f chore(phantun) bump clap and tokio dependencies to latest 2021-12-05 07:01:58 -08:00
Datong Sun
e2a9194f6f chore(fake-tcp) bump to v0.2.2 2021-12-05 06:57:23 -08:00
Datong Sun
d0eaefe5d0 chore(phantun) specify exact version for fake-tcp dependency 2021-12-05 06:49:35 -08:00
Datong Sun
299646a54f chore(fake-tcp) bump to v0.2.1 2021-12-05 06:43:18 -08:00
Datong Sun
8b28cdc6c2 chore(crates) do not include bugfix version in dependency as it prevents
auto update from Dependabot
2021-12-05 22:41:05 +08:00
Datong Sun
a8ad203754 docs(readme) fixed some typos 2021-12-01 15:10:52 +08:00
Datong Sun
33e510e7ba fix(fake-tcp) remove unneeded State::Closed as it is not in a
reachable code path
2021-11-19 10:45:13 -08:00
Datong Sun
521a3f1a01 docs(readme) bump release version and add host name support info for
`--remote`
2021-11-18 20:48:53 -08:00
Datong Sun
c5a5116808 chore(phantun) bump to v0.2.3, bump fake-tcp dependency to v0.2.0 2021-11-18 20:37:28 -08:00
Datong Sun
e8f2457cb5 chore(fake-tcp) bump to v0.2.0 2021-11-18 20:36:05 -08:00
Datong Sun
583cdbe300 perf(fake-tcp) reduce the number of clone() calls in hot path 2021-11-19 12:35:21 +08:00
Datong Sun
91988520e5 feat(*) add DNS name support for --remote argument in both Client and
Server
2021-11-19 12:30:47 +08:00
Datong Sun
49cc6a6865 chore(phantun) update fake-tcp dependency version to v0.1.3 2021-11-02 18:29:53 +08:00
Datong Sun
7390d4bf27 chore(fake-tcp) release version v0.1.3 2021-11-02 18:29:53 +08:00
dependabot[bot]
95e762f5fd chore(deps): update dndx-fork-tokio-tun requirement from 0.3.16 to 0.4.0
Updates the requirements on [dndx-fork-tokio-tun](https://github.com/yaa110/tokio-tun) to permit the latest version.
- [Release notes](https://github.com/yaa110/tokio-tun/releases)
- [Commits](https://github.com/yaa110/tokio-tun/compare/0.3.16...0.4.0)

---
updated-dependencies:
- dependency-name: dndx-fork-tokio-tun
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-02 18:29:53 +08:00
Datong Sun
c9043015f2 docs(readme) update latest release to v0.2.2 2021-10-30 09:32:14 -07:00
17 changed files with 368 additions and 208 deletions

View File

@@ -31,7 +31,7 @@ jobs:
- mipsel-unknown-linux-musl - mipsel-unknown-linux-musl
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable

View File

@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2014-2021 The Rust Project Developers Copyright 2021-2022 Datong Sun (dndx@idndx.com)
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2014-2021 The Rust Project Developers Copyright (c) 2021-2022 Datong Sun (dndx@idndx.com)
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated

View File

@@ -24,6 +24,7 @@ Table of Contents
* [MTU overhead](#mtu-overhead) * [MTU overhead](#mtu-overhead)
* [MTU calculation for WireGuard](#mtu-calculation-for-wireguard) * [MTU calculation for WireGuard](#mtu-calculation-for-wireguard)
* [Version compatibility](#version-compatibility) * [Version compatibility](#version-compatibility)
* [Documentations](#documentations)
* [Performance](#performance) * [Performance](#performance)
* [Future plans](#future-plans) * [Future plans](#future-plans)
* [Compariation to udp2raw](#compariation-to-udp2raw) * [Compariation to udp2raw](#compariation-to-udp2raw)
@@ -31,16 +32,16 @@ Table of Contents
# Latest release # Latest release
[v0.2.1](https://github.com/dndx/phantun/releases/tag/v0.2.1) [v0.2.5](https://github.com/dndx/phantun/releases/tag/v0.2.5)
# Overview # Overview
Phanton is a project that obfuscated UDP packets into TCP connections. It aims to Phantun is a project that obfuscated UDP packets into TCP connections. It aims to
achieve maximum performance with minimum processing and encapsulation overhead. achieve maximum performance with minimum processing and encapsulation overhead.
It is commonly used in environments where UDP is blocked/throttled but TCP is allowed through. It is commonly used in environments where UDP is blocked/throttled but TCP is allowed through.
Phanton simply converts a stream of UDP packets into obfuscated TCP stream packets. The TCP stack Phantun simply converts a stream of UDP packets into obfuscated TCP stream packets. The TCP stack
used by Phantun is designed to pass through most L3/L4 stateful/stateless firewalls/NAT used by Phantun is designed to pass through most L3/L4 stateful/stateless firewalls/NAT
devices. It will **not** be able to pass through L7 proxies. devices. It will **not** be able to pass through L7 proxies.
However, the advantage of this approach is that none of the common UDP over TCP performance killer However, the advantage of this approach is that none of the common UDP over TCP performance killer
@@ -71,7 +72,7 @@ NIC address and Phantun's TUN interface address.
You may customize the name of Tun interface created by Phantun and the assigned addresses. Please You may customize the name of Tun interface created by Phantun and the assigned addresses. Please
run the executable with `-h` options to see how to change them. run the executable with `-h` options to see how to change them.
Another way to help understand this network topology: Another way to help understand this network topology (please see the diagram above for an illustration of this topology):
Phantun Client is like a machine with private IP address (`192.168.200.2`) behind a router. Phantun Client is like a machine with private IP address (`192.168.200.2`) behind a router.
In order for it to reach the Internet, you will need to SNAT the private IP address before it's traffic In order for it to reach the Internet, you will need to SNAT the private IP address before it's traffic
@@ -134,7 +135,7 @@ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Server needs to DNAT the TCP listening port to Phantun's TUN interface address. Server needs to DNAT the TCP listening port to Phantun's TUN interface address.
Note: change `eth0` to whatever actual physical interface name is and `4567` to Note: change `eth0` to whatever actual physical interface name is and `4567` to
actual TCP port number used by Phanton server actual TCP port number used by Phantun server
[Back to TOC](#table-of-contents) [Back to TOC](#table-of-contents)
@@ -176,6 +177,8 @@ sudo setcap cap_net_admin=+pe phantun_client
**Note:** Run Phantun executable with `-h` option to see full detailed options. **Note:** Run Phantun executable with `-h` option to see full detailed options.
[Back to TOC](#table-of-contents)
### Server ### Server
Note: `4567` is the TCP port Phantun should listen on and must corresponds to the DNAT Note: `4567` is the TCP port Phantun should listen on and must corresponds to the DNAT
@@ -185,6 +188,12 @@ rule specified above. `127.0.0.1:1234` is the UDP Server to connect to for new c
RUST_LOG=info /usr/local/bin/phantun_server --local 4567 --remote 127.0.0.1:1234 RUST_LOG=info /usr/local/bin/phantun_server --local 4567 --remote 127.0.0.1:1234
``` ```
Or use host name with `--remote`:
```
RUST_LOG=info /usr/local/bin/phantun_server --local 4567 --remote example.com:1234
```
[Back to TOC](#table-of-contents) [Back to TOC](#table-of-contents)
### Client ### Client
@@ -196,6 +205,12 @@ the Phantun Server to connect.
RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote 10.0.0.1:4567 RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote 10.0.0.1:4567
``` ```
Or use host name with `--remote`:
```
RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote example.com:4567
```
[Back to TOC](#table-of-contents) [Back to TOC](#table-of-contents)
# MTU overhead # MTU overhead
@@ -240,6 +255,13 @@ of Server/Client of Phantun on both ends to ensure maximum compatibility.
[Back to TOC](#table-of-contents) [Back to TOC](#table-of-contents)
# Documentations
For users who wish to use `fake-tcp` library inside their own project, refer to the documentations for the library at:
[https://docs.rs/fake-tcp](https://docs.rs/fake-tcp).
[Back to TOC](#table-of-contents)
# Performance # Performance
Performance was tested on AWS t3.xlarge instance with 4 vCPUs and 5 Gb/s NIC. WireGuard was used Performance was tested on AWS t3.xlarge instance with 4 vCPUs and 5 Gb/s NIC. WireGuard was used
@@ -264,7 +286,7 @@ for tunneling TCP/UDP traffic between two test instances and MTU has been tuned
# Compariation to udp2raw # Compariation to udp2raw
[udp2raw](https://github.com/wangyu-/udp2raw-tunnel) is another popular project by [@wangyu-](https://github.com/wangyu-) [udp2raw](https://github.com/wangyu-/udp2raw-tunnel) is another popular project by [@wangyu-](https://github.com/wangyu-)
that is very similar to what Phantun can do. In fact I took inspirations of Phantun from udp2raw. The biggest reason for that is very similar to what Phantun can do. In fact I took inspirations of Phantun from udp2raw. The biggest reason for
developing Phanton is because of lack of performance when running udp2raw (especially on multi-core systems such as Raspberry Pi). developing Phantun is because of lack of performance when running udp2raw (especially on multi-core systems such as Raspberry Pi).
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 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
and UDP over UDP mode are not supported and there is no anti-replay nor encryption support. The benefit of this is much better and UDP over UDP mode are not supported and there is no anti-replay nor encryption support. The benefit of this is much better
performance overall and less MTU overhead because lack of additional headers inside the TCP payload. performance overall and less MTU overhead because lack of additional headers inside the TCP payload.
@@ -288,7 +310,7 @@ Here is a quick overview of comparison between those two to help you choose:
# License # License
Copyright 2021 Datong Sun <dndx@idndx.com> Copyright 2021-2022 Datong Sun (dndx@idndx.com)
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "fake-tcp" name = "fake-tcp"
version = "0.1.2" version = "0.2.4"
edition = "2021" edition = "2021"
authors = ["Datong Sun <dndx@idndx.com>"] authors = ["Datong Sun <dndx@idndx.com>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@@ -16,9 +16,9 @@ benchmark = []
[dependencies] [dependencies]
bytes = "1" bytes = "1"
pnet = "0.28.0" pnet = "0.29"
tokio = { version = "1.12.0", features = ["full"] } tokio = { version = "1.14", features = ["full"] }
rand = { version = "0.8.4", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
log = "0.4" log = "0.4"
internet-checksum = "0.2.0" internet-checksum = "0.2"
dndx-fork-tokio-tun = "0.3.16" tokio-tun = "0.5"

View File

@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2014-2021 The Rust Project Developers Copyright 2021-2022 Datong Sun (dndx@idndx.com)
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2014-2021 The Rust Project Developers Copyright (c) 2021-2022 Datong Sun (dndx@idndx.com)
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated

View File

@@ -1,7 +1,46 @@
//! A minimum, userspace TCP based datagram stack
//!
//! # Overview
//!
//! `fake-tcp` is a reusable library that implements a minimum TCP stack in
//! user space using the Tun interface. It allows programs to send datagrams
//! as if they are part of a TCP connection. `fake-tcp` has been tested to
//! be able to pass through a variety of NAT and stateful firewalls while
//! fully preserves certain desirable behavior such as out of order delivery
//! and no congestion/flow controls.
//!
//! # Core Concepts
//!
//! The core of the `fake-tcp` crate compose of two structures. [`Stack`] and
//! [`Socket`].
//!
//! ## [`Stack`]
//!
//! [`Stack`] represents a virtual TCP stack that operates at
//! Layer 3. It is responsible for:
//!
//! * TCP active and passive open and handshake
//! * `RST` handling
//! * Interact with the Tun interface at Layer 3
//! * Distribute incoming datagrams to corresponding [`Socket`]
//!
//! ## [`Socket`]
//!
//! [`Socket`] represents a TCP connection. It registers the identifying
//! tuple `(src_ip, src_port, dest_ip, dest_port)` inside the [`Stack`] so
//! so that incoming packets can be distributed to the right [`Socket`] with
//! using a channel. It is also what the client should use for
//! sending/receiving datagrams.
//!
//! # Examples
//!
//! Please see [`client.rs`](https://github.com/dndx/phantun/blob/main/phantun/src/bin/client.rs)
//! and [`server.rs`](https://github.com/dndx/phantun/blob/main/phantun/src/bin/server.rs) files
//! from the `phantun` crate for how to use this library in client/server mode, respectively.
#![cfg_attr(feature = "benchmark", feature(test))] #![cfg_attr(feature = "benchmark", feature(test))]
pub mod packet; pub mod packet;
extern crate dndx_fork_tokio_tun as tokio_tun;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use log::{error, info, trace, warn}; use log::{error, info, trace, warn};
@@ -15,7 +54,6 @@ use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::sync::mpsc::{self, Receiver, Sender};
use tokio::sync::watch;
use tokio::sync::Mutex as AsyncMutex; use tokio::sync::Mutex as AsyncMutex;
use tokio::time; use tokio::time;
use tokio_tun::Tun; use tokio_tun::Tun;
@@ -25,7 +63,7 @@ const RETRIES: usize = 6;
const MPSC_BUFFER_LEN: usize = 512; const MPSC_BUFFER_LEN: usize = 512;
#[derive(Hash, Eq, PartialEq, Clone, Debug)] #[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct AddrTuple { struct AddrTuple {
local_addr: SocketAddrV4, local_addr: SocketAddrV4,
remote_addr: SocketAddrV4, remote_addr: SocketAddrV4,
} }
@@ -69,10 +107,15 @@ pub struct Socket {
seq: AtomicU32, seq: AtomicU32,
ack: AtomicU32, ack: AtomicU32,
state: State, state: State,
closing_tx: watch::Sender<()>,
closing_rx: watch::Receiver<()>,
} }
/// A socket that represents a unique TCP connection between a server and client.
///
/// The `Socket` object itself satisfies `Sync` and `Send`, which means it can
/// be safely called within an async future.
///
/// To close a TCP connection that is no longer needed, simply drop this object
/// out of scope.
impl Socket { impl Socket {
fn new( fn new(
shared: Arc<Shared>, shared: Arc<Shared>,
@@ -83,7 +126,6 @@ impl Socket {
state: State, state: State,
) -> (Socket, Sender<Bytes>) { ) -> (Socket, Sender<Bytes>) {
let (incoming_tx, incoming_rx) = mpsc::channel(MPSC_BUFFER_LEN); let (incoming_tx, incoming_rx) = mpsc::channel(MPSC_BUFFER_LEN);
let (closing_tx, closing_rx) = watch::channel(());
( (
Socket { Socket {
@@ -95,8 +137,6 @@ impl Socket {
seq: AtomicU32::new(0), seq: AtomicU32::new(0),
ack: AtomicU32::new(ack.unwrap_or(0)), ack: AtomicU32::new(ack.unwrap_or(0)),
state, state,
closing_tx,
closing_rx,
}, },
incoming_tx, incoming_tx,
) )
@@ -113,9 +153,14 @@ impl Socket {
) )
} }
/// Sends a datagram to the other end.
///
/// This method takes `&self`, and it can be called safely by multiple threads
/// at the same time.
///
/// A return of `None` means the Tun socket returned an error
/// and this socket must be closed.
pub async fn send(&self, payload: &[u8]) -> Option<()> { pub async fn send(&self, payload: &[u8]) -> Option<()> {
let mut closing = self.closing_rx.clone();
match self.state { match self.state {
State::Established => { State::Established => {
let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, Some(payload)); let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, Some(payload));
@@ -123,56 +168,47 @@ impl Socket {
tokio::select! { tokio::select! {
res = self.tun.send(&buf) => { res = self.tun.send(&buf) => {
res.unwrap(); res.ok().and(Some(()))
Some(())
}, },
_ = closing.changed() => {
None
}
} }
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
/// Attempt to receive a datagram from the other end.
///
/// This method takes `&self`, and it can be called safely by multiple threads
/// at the same time.
///
/// A return of `None` means the TCP connection is broken
/// and this socket must be closed.
pub async fn recv(&self, buf: &mut [u8]) -> Option<usize> { pub async fn recv(&self, buf: &mut [u8]) -> Option<usize> {
let mut closing = self.closing_rx.clone();
match self.state { match self.state {
State::Established => { State::Established => {
let mut incoming = self.incoming.lock().await; let mut incoming = self.incoming.lock().await;
tokio::select! { incoming.recv().await.and_then(|raw_buf| {
Some(raw_buf) = incoming.recv() => { let (_v4_packet, tcp_packet) = parse_ipv4_packet(&raw_buf);
let (_v4_packet, tcp_packet) = parse_ipv4_packet(&raw_buf);
if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 { if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
info!("Connection {} reset by peer", self); info!("Connection {} reset by peer", self);
self.close(); return None;
return None;
}
let payload = tcp_packet.payload();
self.ack
.store(tcp_packet.get_sequence().wrapping_add(1), Ordering::Relaxed);
buf[..payload.len()].copy_from_slice(payload);
Some(payload.len())
},
_ = closing.changed() => {
None
} }
}
let payload = tcp_packet.payload();
self.ack
.store(tcp_packet.get_sequence().wrapping_add(1), Ordering::Relaxed);
buf[..payload.len()].copy_from_slice(payload);
Some(payload.len())
})
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
pub fn close(&self) {
self.closing_tx.send(()).unwrap();
}
async fn accept(mut self) { async fn accept(mut self) {
for _ in 0..RETRIES { for _ in 0..RETRIES {
match self.state { match self.state {
@@ -271,6 +307,7 @@ impl Socket {
} }
impl Drop for Socket { impl Drop for Socket {
/// Drop the socket and close the TCP connection
fn drop(&mut self) { fn drop(&mut self) {
let tuple = AddrTuple::new(self.local_addr, self.remote_addr); let tuple = AddrTuple::new(self.local_addr, self.remote_addr);
// dissociates ourself from the dispatch map // dissociates ourself from the dispatch map
@@ -282,12 +319,13 @@ impl Drop for Socket {
if let Err(e) = self.tun.try_send(&buf) { if let Err(e) = self.tun.try_send(&buf) {
warn!("Unable to send RST to remote end: {}", e); warn!("Unable to send RST to remote end: {}", e);
} }
self.close();
info!("Fake TCP connection to {} closed", self); info!("Fake TCP connection to {} closed", self);
} }
} }
impl fmt::Display for Socket { impl fmt::Display for Socket {
/// User-friendly string representation of the socket
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
@@ -297,7 +335,12 @@ impl fmt::Display for Socket {
} }
} }
/// A userspace TCP state machine
impl Stack { impl Stack {
/// Create a new stack, `tun` is an array of [`Tun`](tokio_tun::Tun).
/// When more than one [`Tun`](tokio_tun::Tun) object is passed in, same amount
/// of reader will be spawned later. This allows user to utilize the performance
/// benefit of Multiqueue Tun support on machines with SMP.
pub fn new(tun: Vec<Tun>) -> Stack { pub fn new(tun: Vec<Tun>) -> Stack {
let tun: Vec<Arc<Tun>> = tun.into_iter().map(Arc::new).collect(); let tun: Vec<Arc<Tun>> = tun.into_iter().map(Arc::new).collect();
let (ready_tx, ready_rx) = mpsc::channel(MPSC_BUFFER_LEN); let (ready_tx, ready_rx) = mpsc::channel(MPSC_BUFFER_LEN);
@@ -326,14 +369,18 @@ impl Stack {
} }
} }
/// Listens for incoming connections on the given `port`.
pub fn listen(&mut self, port: u16) { pub fn listen(&mut self, port: u16) {
assert!(self.shared.listening.write().unwrap().insert(port)); assert!(self.shared.listening.write().unwrap().insert(port));
} }
/// Accepts an incoming connection.
pub async fn accept(&mut self) -> Socket { pub async fn accept(&mut self) -> Socket {
self.ready.recv().await.unwrap() self.ready.recv().await.unwrap()
} }
/// Connects to the remote end. `None` returned means
/// the connection attempt failed.
pub async fn connect(&mut self, addr: SocketAddrV4) -> Option<Socket> { pub async fn connect(&mut self, addr: SocketAddrV4) -> Option<Socket> {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let local_port: u16 = rng.gen_range(1024..65535); let local_port: u16 = rng.gen_range(1024..65535);
@@ -396,11 +443,10 @@ impl Stack {
} else { } else {
trace!("Cache miss, checking the shared tuples table for connection"); trace!("Cache miss, checking the shared tuples table for connection");
let sender; let sender = {
{
let tuples = shared.tuples.read().unwrap(); let tuples = shared.tuples.read().unwrap();
sender = tuples.get(&tuple).cloned(); tuples.get(&tuple).cloned()
} };
if let Some(c) = sender { if let Some(c) = sender {
trace!("Storing connection information into local tuples"); trace!("Storing connection information into local tuples");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "phantun" name = "phantun"
version = "0.2.2" version = "0.3.0"
edition = "2021" edition = "2021"
authors = ["Datong Sun <dndx@idndx.com>"] authors = ["Datong Sun <dndx@idndx.com>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@@ -11,11 +11,12 @@ Transforms UDP stream into (fake) TCP streams that can go through
Layer 3 & Layer 4 (NAPT) firewalls/NATs. Layer 3 & Layer 4 (NAPT) firewalls/NATs.
""" """
[dependencies] [dependencies]
clap = "2.33.3" clap = { version = "3.0", features = ["cargo"] }
socket2 = { version = "0.4.2", features = ["all"] } socket2 = { version = "0.4", features = ["all"] }
fake-tcp = "0.1.2" fake-tcp = { path = "../fake-tcp", version = "0.2" }
tokio = { version = "1.12.0", features = ["full"] } tokio = { version = "1.14", features = ["full"] }
tokio-util = "0.7"
log = "0.4" log = "0.4"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4"
dndx-fork-tokio-tun = "0.3.16" tokio-tun = "0.5"
num_cpus = "1.13.0" num_cpus = "1.13"

View File

@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2014-2021 The Rust Project Developers Copyright 2021-2022 Datong Sun (dndx@idndx.com)
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2014-2021 The Rust Project Developers Copyright (c) 2021-2022 Datong Sun (dndx@idndx.com)
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated

View File

@@ -1,51 +1,28 @@
extern crate dndx_fork_tokio_tun as tokio_tun; use clap::{crate_version, Arg, Command};
use clap::{crate_version, App, Arg};
use fake_tcp::packet::MAX_PACKET_LEN; use fake_tcp::packet::MAX_PACKET_LEN;
use fake_tcp::{Socket, Stack}; use fake_tcp::{Socket, Stack};
use log::{debug, error, info}; use log::{debug, error, info};
use phantun::utils::new_udp_reuseport;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::net::{Ipv4Addr, SocketAddr};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use tokio::sync::{Notify, RwLock};
use tokio::net::UdpSocket;
use tokio::sync::RwLock;
use tokio::time; use tokio::time;
use tokio_tun::TunBuilder; use tokio_tun::TunBuilder;
use tokio_util::sync::CancellationToken;
const UDP_TTL: Duration = Duration::from_secs(180); use phantun::UDP_TTL;
fn new_udp_reuseport(addr: SocketAddr) -> UdpSocket {
let udp_sock = socket2::Socket::new(
if addr.is_ipv4() {
socket2::Domain::IPV4
} else {
socket2::Domain::IPV6
},
socket2::Type::DGRAM,
None,
)
.unwrap();
udp_sock.set_reuse_port(true).unwrap();
// from tokio-rs/mio/blob/master/src/sys/unix/net.rs
udp_sock.set_cloexec(true).unwrap();
udp_sock.set_nonblocking(true).unwrap();
udp_sock.bind(&socket2::SockAddr::from(addr)).unwrap();
let udp_sock: std::net::UdpSocket = udp_sock.into();
udp_sock.try_into().unwrap()
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
let matches = App::new("Phantun Client") let matches = Command::new("Phantun Client")
.version(crate_version!()) .version(crate_version!())
.author("Datong Sun (github.com/dndx)") .author("Datong Sun (github.com/dndx)")
.arg( .arg(
Arg::with_name("local") Arg::new("local")
.short("l") .short('l')
.long("local") .long("local")
.required(true) .required(true)
.value_name("IP:PORT") .value_name("IP:PORT")
@@ -53,16 +30,16 @@ async fn main() {
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("remote") Arg::new("remote")
.short("r") .short('r')
.long("remote") .long("remote")
.required(true) .required(true)
.value_name("IP:PORT") .value_name("IP or HOST NAME:PORT")
.help("Sets the address and port where Phantun Client connects to Phantun Server") .help("Sets the address or host name and port where Phantun Client connects to Phantun Server")
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("tun") Arg::new("tun")
.long("tun") .long("tun")
.required(false) .required(false)
.value_name("tunX") .value_name("tunX")
@@ -71,7 +48,7 @@ async fn main() {
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("tun_local") Arg::new("tun_local")
.long("tun-local") .long("tun-local")
.required(false) .required(false)
.value_name("IP") .value_name("IP")
@@ -80,7 +57,7 @@ async fn main() {
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("tun_peer") Arg::new("tun_peer")
.long("tun-peer") .long("tun-peer")
.required(false) .required(false)
.value_name("IP") .value_name("IP")
@@ -97,11 +74,19 @@ async fn main() {
.unwrap() .unwrap()
.parse() .parse()
.expect("bad local address"); .expect("bad local address");
let remote_addr: SocketAddrV4 = matches
.value_of("remote") let remote_addr = tokio::net::lookup_host(matches.value_of("remote").unwrap())
.unwrap() .await
.parse() .expect("bad remote address or host")
.expect("bad remote address"); .find(|addr| addr.is_ipv4())
.expect("unable to resolve remote host name or no valid A record was returned");
let remote_addr = if let SocketAddr::V4(addr) = remote_addr {
addr
} else {
unreachable!();
};
info!("Remote address is: {}", remote_addr);
let tun_local: Ipv4Addr = matches let tun_local: Ipv4Addr = matches
.value_of("tun_local") .value_of("tun_local")
.unwrap() .unwrap()
@@ -113,6 +98,8 @@ async fn main() {
.parse() .parse()
.expect("bad peer address for Tun interface"); .expect("bad peer address for Tun interface");
let num_cpus = num_cpus::get();
let tun = TunBuilder::new() let tun = TunBuilder::new()
.name(matches.value_of("tun").unwrap()) // if name is empty, then it is set by kernel. .name(matches.value_of("tun").unwrap()) // if name is empty, then it is set by kernel.
.tap(false) // false (default): TUN, true: TAP. .tap(false) // false (default): TUN, true: TAP.
@@ -120,7 +107,7 @@ async fn main() {
.up() // or set it up manually using `sudo ip link set <tun-name> up`. .up() // or set it up manually using `sudo ip link set <tun-name> up`.
.address(tun_local) .address(tun_local)
.destination(tun_peer) .destination(tun_peer)
.try_build_mq(num_cpus::get()) .try_build_mq(num_cpus)
.unwrap(); .unwrap();
info!("Created TUN device {}", tun[0].name()); info!("Created TUN device {}", tun[0].name());
@@ -162,52 +149,85 @@ async fn main() {
assert!(connections.write().await.insert(addr, sock.clone()).is_none()); assert!(connections.write().await.insert(addr, sock.clone()).is_none());
debug!("inserted fake TCP socket into connection table"); debug!("inserted fake TCP socket into connection table");
let connections = connections.clone();
// spawn "fastpath" UDP socket and task, this will offload main task // spawn "fastpath" UDP socket and task, this will offload main task
// from forwarding UDP packets // from forwarding UDP packets
tokio::spawn(async move {
let mut buf_udp = [0u8; MAX_PACKET_LEN];
let mut buf_tcp = [0u8; MAX_PACKET_LEN];
let udp_sock = new_udp_reuseport(local_addr);
udp_sock.connect(addr).await.unwrap();
let packet_received = Arc::new(Notify::new());
let quit = CancellationToken::new();
for i in 0..num_cpus {
let sock = sock.clone();
let quit = quit.child_token();
let packet_received = packet_received.clone();
tokio::spawn(async move {
let mut buf_udp = [0u8; MAX_PACKET_LEN];
let mut buf_tcp = [0u8; MAX_PACKET_LEN];
let udp_sock = new_udp_reuseport(local_addr);
udp_sock.connect(addr).await.unwrap();
loop {
tokio::select! {
Ok(size) = udp_sock.recv(&mut buf_udp) => {
if sock.send(&buf_udp[..size]).await.is_none() {
debug!("removed fake TCP socket from connections table");
quit.cancel();
return;
}
packet_received.notify_one();
},
res = sock.recv(&mut buf_tcp) => {
match res {
Some(size) => {
if size > 0 {
if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
error!("Unable to send UDP packet to {}: {}, closing connection", e, addr);
quit.cancel();
return;
}
}
},
None => {
debug!("removed fake TCP socket from connections table");
quit.cancel();
return;
},
}
packet_received.notify_one();
},
_ = quit.cancelled() => {
debug!("worker {} terminated", i);
return;
},
};
}
});
}
let connections = connections.clone();
tokio::spawn(async move {
loop { loop {
let read_timeout = time::sleep(UDP_TTL); let read_timeout = time::sleep(UDP_TTL);
let packet_received_fut = packet_received.notified();
tokio::select! { tokio::select! {
Ok(size) = udp_sock.recv(&mut buf_udp) => {
if sock.send(&buf_udp[..size]).await.is_none() {
connections.write().await.remove(&addr);
debug!("removed fake TCP socket from connections table");
return;
}
},
res = sock.recv(&mut buf_tcp) => {
match res {
Some(size) => {
if size > 0 {
if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
connections.write().await.remove(&addr);
error!("Unable to send UDP packet to {}: {}, closing connection", e, addr);
return;
}
}
},
None => {
connections.write().await.remove(&addr);
debug!("removed fake TCP socket from connections table");
return;
},
}
},
_ = read_timeout => { _ = read_timeout => {
info!("No traffic seen in the last {:?}, closing connection", UDP_TTL); info!("No traffic seen in the last {:?}, closing connection", UDP_TTL);
connections.write().await.remove(&addr); connections.write().await.remove(&addr);
debug!("removed fake TCP socket from connections table"); debug!("removed fake TCP socket from connections table");
quit.cancel();
return; return;
} },
}; _ = quit.cancelled() => {
connections.write().await.remove(&addr);
debug!("removed fake TCP socket from connections table");
return;
},
_ = packet_received_fut => {},
}
} }
}); });
}, },

View File

@@ -1,25 +1,28 @@
extern crate dndx_fork_tokio_tun as tokio_tun; use clap::{crate_version, Arg, Command};
use clap::{crate_version, App, Arg};
use fake_tcp::packet::MAX_PACKET_LEN; use fake_tcp::packet::MAX_PACKET_LEN;
use fake_tcp::Stack; use fake_tcp::Stack;
use log::{error, info}; use log::{debug, error, info};
use std::net::{Ipv4Addr, SocketAddr}; use phantun::utils::new_udp_reuseport;
use std::net::Ipv4Addr;
use std::sync::Arc;
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use tokio::time::{self, Duration}; use tokio::sync::Notify;
use tokio::time;
use tokio_tun::TunBuilder; use tokio_tun::TunBuilder;
const UDP_TTL: Duration = Duration::from_secs(180); use tokio_util::sync::CancellationToken;
use phantun::UDP_TTL;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
let matches = App::new("Phantun Server") let matches = Command::new("Phantun Server")
.version(crate_version!()) .version(crate_version!())
.author("Datong Sun (github.com/dndx)") .author("Datong Sun (github.com/dndx)")
.arg( .arg(
Arg::with_name("local") Arg::new("local")
.short("l") .short('l')
.long("local") .long("local")
.required(true) .required(true)
.value_name("PORT") .value_name("PORT")
@@ -27,16 +30,16 @@ async fn main() {
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("remote") Arg::new("remote")
.short("r") .short('r')
.long("remote") .long("remote")
.required(true) .required(true)
.value_name("IP:PORT") .value_name("IP or HOST NAME:PORT")
.help("Sets the address and port where Phantun Server forwards UDP packets to, IPv6 address need to be specified as: \"[IPv6]:PORT\"") .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\"")
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("tun") Arg::new("tun")
.long("tun") .long("tun")
.required(false) .required(false)
.value_name("tunX") .value_name("tunX")
@@ -45,7 +48,7 @@ async fn main() {
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("tun_local") Arg::new("tun_local")
.long("tun-local") .long("tun-local")
.required(false) .required(false)
.value_name("IP") .value_name("IP")
@@ -54,7 +57,7 @@ async fn main() {
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("tun_peer") Arg::new("tun_peer")
.long("tun-peer") .long("tun-peer")
.required(false) .required(false)
.value_name("IP") .value_name("IP")
@@ -71,11 +74,14 @@ async fn main() {
.unwrap() .unwrap()
.parse() .parse()
.expect("bad local port"); .expect("bad local port");
let remote_addr: SocketAddr = matches
.value_of("remote") let remote_addr = tokio::net::lookup_host(matches.value_of("remote").unwrap())
.unwrap() .await
.parse() .expect("bad remote address or host")
.expect("bad remote address"); .next()
.expect("unable to resolve remote host name");
info!("Remote address is: {}", remote_addr);
let tun_local: Ipv4Addr = matches let tun_local: Ipv4Addr = matches
.value_of("tun_local") .value_of("tun_local")
.unwrap() .unwrap()
@@ -87,6 +93,8 @@ async fn main() {
.parse() .parse()
.expect("bad peer address for Tun interface"); .expect("bad peer address for Tun interface");
let num_cpus = num_cpus::get();
let tun = TunBuilder::new() let tun = TunBuilder::new()
.name(matches.value_of("tun").unwrap()) // if name is empty, then it is set by kernel. .name(matches.value_of("tun").unwrap()) // if name is empty, then it is set by kernel.
.tap(false) // false (default): TUN, true: TAP. .tap(false) // false (default): TUN, true: TAP.
@@ -94,7 +102,7 @@ async fn main() {
.up() // or set it up manually using `sudo ip link set <tun-name> up`. .up() // or set it up manually using `sudo ip link set <tun-name> up`.
.address(tun_local) .address(tun_local)
.destination(tun_peer) .destination(tun_peer)
.try_build_mq(num_cpus::get()) .try_build_mq(num_cpus)
.unwrap(); .unwrap();
info!("Created TUN device {}", tun[0].name()); info!("Created TUN device {}", tun[0].name());
@@ -109,46 +117,82 @@ async fn main() {
let mut buf_tcp = [0u8; MAX_PACKET_LEN]; let mut buf_tcp = [0u8; MAX_PACKET_LEN];
loop { loop {
let sock = stack.accept().await; let sock = Arc::new(stack.accept().await);
info!("New connection: {}", sock); info!("New connection: {}", sock);
tokio::spawn(async move { let packet_received = Arc::new(Notify::new());
let udp_sock = UdpSocket::bind(if remote_addr.is_ipv4() { let quit = CancellationToken::new();
"0.0.0.0:0" let udp_sock = UdpSocket::bind(if remote_addr.is_ipv4() {
} else { "0.0.0.0:0"
"[::]:0" } else {
}) "[::]:0"
.await })
.unwrap(); .await
udp_sock.connect(remote_addr).await.unwrap(); .unwrap();
let local_addr = udp_sock.local_addr().unwrap();
drop(udp_sock);
for i in 0..num_cpus {
let sock = sock.clone();
let quit = quit.child_token();
let packet_received = packet_received.clone();
let udp_sock = new_udp_reuseport(local_addr);
tokio::spawn(async move {
udp_sock.connect(remote_addr).await.unwrap();
loop {
tokio::select! {
Ok(size) = udp_sock.recv(&mut buf_udp) => {
if sock.send(&buf_udp[..size]).await.is_none() {
quit.cancel();
return;
}
packet_received.notify_one();
},
res = sock.recv(&mut buf_tcp) => {
match res {
Some(size) => {
if size > 0 {
if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
error!("Unable to send UDP packet to {}: {}, closing connection", e, remote_addr);
quit.cancel();
return;
}
}
},
None => {
quit.cancel();
return;
},
}
packet_received.notify_one();
},
_ = quit.cancelled() => {
debug!("worker {} terminated", i);
return;
},
};
}
});
}
tokio::spawn(async move {
loop { loop {
let read_timeout = time::sleep(UDP_TTL); let read_timeout = time::sleep(UDP_TTL);
let packet_received_fut = packet_received.notified();
tokio::select! { tokio::select! {
Ok(size) = udp_sock.recv(&mut buf_udp) => {
if sock.send(&buf_udp[..size]).await.is_none() {
return;
}
},
res = sock.recv(&mut buf_tcp) => {
match res {
Some(size) => {
if size > 0 {
if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
error!("Unable to send UDP packet to {}: {}, closing connection", e, remote_addr);
return;
}
}
},
None => { return; },
}
},
_ = read_timeout => { _ = read_timeout => {
info!("No traffic seen in the last {:?}, closing connection", UDP_TTL); info!("No traffic seen in the last {:?}, closing connection", UDP_TTL);
quit.cancel();
return; return;
} },
}; _ = packet_received_fut => {},
}
} }
}); });
} }

5
phantun/src/lib.rs Normal file
View File

@@ -0,0 +1,5 @@
use std::time::Duration;
pub mod utils;
pub const UDP_TTL: Duration = Duration::from_secs(180);

22
phantun/src/utils.rs Normal file
View File

@@ -0,0 +1,22 @@
use std::net::SocketAddr;
use tokio::net::UdpSocket;
pub fn new_udp_reuseport(local_addr: SocketAddr) -> UdpSocket {
let udp_sock = socket2::Socket::new(
if local_addr.is_ipv4() {
socket2::Domain::IPV4
} else {
socket2::Domain::IPV6
},
socket2::Type::DGRAM,
None,
)
.unwrap();
udp_sock.set_reuse_port(true).unwrap();
// from tokio-rs/mio/blob/master/src/sys/unix/net.rs
udp_sock.set_cloexec(true).unwrap();
udp_sock.set_nonblocking(true).unwrap();
udp_sock.bind(&socket2::SockAddr::from(local_addr)).unwrap();
let udp_sock: std::net::UdpSocket = udp_sock.into();
udp_sock.try_into().unwrap()
}