23 Commits

Author SHA1 Message Date
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
Datong Sun
494abf37c5 docs(readme) add IPv6 support info 2021-10-30 09:21:23 -07:00
Datong Sun
cab87bd75b chore(cargo) bump Phantun to v0.2.2 2021-10-30 09:15:23 -07:00
Datong Sun
042f5af49f feat(phantun) add support for connecting to/from IPv6 based UDP endpoint 2021-10-31 00:09:02 +08:00
Datong Sun
f667f56747 chore(actions) always use the latest stable toolchain 2021-10-21 03:47:57 -07:00
Datong Sun
49665b906f chore(cargo) use Rust edition 2021 2021-10-21 03:41:42 -07:00
Datong Sun
e9cde27923 ci(dependabot) add Dependabot checks 2021-10-20 02:07:05 -07:00
8 changed files with 123 additions and 91 deletions

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"

View File

@@ -12,6 +12,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Run lint
run: cargo clippy --verbose
- name: Build

View File

@@ -31,16 +31,16 @@ Table of Contents
# Latest release
[v0.2.1](https://github.com/dndx/phantun/releases/tag/v0.2.1)
[v0.2.3](https://github.com/dndx/phantun/releases/tag/v0.2.3)
# 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.
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
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
@@ -84,6 +84,10 @@ and change the destination IP address to where the server is listening for incom
In those cases, the machine/iptables running Phantun acts as the "router" that allows Phantun
to communicate with outside using it's private IP addresses.
As of Phantun v0.2.2, IPv6 support for UDP endpoints has been added, however Fake TCP IPv6 support
has not been finished yet. To specify an IPv6 address, use the following format: `[::1]:1234` with
the command line options.
[Back to TOC](#table-of-contents)
## 1. Enable Kernel IP forwarding
@@ -130,7 +134,7 @@ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
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
actual TCP port number used by Phanton server
actual TCP port number used by Phantun server
[Back to TOC](#table-of-contents)
@@ -181,6 +185,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
```
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)
### Client
@@ -192,6 +202,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
```
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)
# MTU overhead
@@ -250,7 +266,7 @@ for tunneling TCP/UDP traffic between two test instances and MTU has been tuned
# Future plans
* IPv6 support
* IPv6 support for fake-tcp
* Load balancing a single UDP stream into multiple TCP streams
* Integration tests
* Auto insertion/removal of required firewall rules
@@ -260,7 +276,7 @@ for tunneling TCP/UDP traffic between two test instances and MTU has been tuned
# Compariation to udp2raw
[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
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
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.
@@ -278,7 +294,7 @@ Here is a quick overview of comparison between those two to help you choose:
| Tunneling MTU overhead | 12 bytes | 44 bytes |
| Seprate TCP connections for each UDP connection | Client/Server | Server only |
| Anti-replay, encryption | ❌ | ✅ |
| IPv6 | Planned | ✅ |
| IPv6 | UDP only | ✅ |
[Back to TOC](#table-of-contents)

View File

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

View File

@@ -15,7 +15,6 @@ use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, RwLock};
use tokio::sync::broadcast;
use tokio::sync::mpsc::{self, Receiver, Sender};
use tokio::sync::watch;
use tokio::sync::Mutex as AsyncMutex;
use tokio::time;
use tokio_tun::Tun;
@@ -69,8 +68,6 @@ pub struct Socket {
seq: AtomicU32,
ack: AtomicU32,
state: State,
closing_tx: watch::Sender<()>,
closing_rx: watch::Receiver<()>,
}
impl Socket {
@@ -83,7 +80,6 @@ impl Socket {
state: State,
) -> (Socket, Sender<Bytes>) {
let (incoming_tx, incoming_rx) = mpsc::channel(MPSC_BUFFER_LEN);
let (closing_tx, closing_rx) = watch::channel(());
(
Socket {
@@ -95,8 +91,6 @@ impl Socket {
seq: AtomicU32::new(0),
ack: AtomicU32::new(ack.unwrap_or(0)),
state,
closing_tx,
closing_rx,
},
incoming_tx,
)
@@ -114,8 +108,6 @@ impl Socket {
}
pub async fn send(&self, payload: &[u8]) -> Option<()> {
let mut closing = self.closing_rx.clone();
match self.state {
State::Established => {
let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, Some(payload));
@@ -123,12 +115,8 @@ impl Socket {
tokio::select! {
res = self.tun.send(&buf) => {
res.unwrap();
Some(())
res.ok().and(Some(()))
},
_ = closing.changed() => {
None
}
}
}
_ => unreachable!(),
@@ -136,43 +124,31 @@ impl Socket {
}
pub async fn recv(&self, buf: &mut [u8]) -> Option<usize> {
let mut closing = self.closing_rx.clone();
match self.state {
State::Established => {
let mut incoming = self.incoming.lock().await;
tokio::select! {
Some(raw_buf) = incoming.recv() => {
let (_v4_packet, tcp_packet) = parse_ipv4_packet(&raw_buf);
incoming.recv().await.and_then(|raw_buf| {
let (_v4_packet, tcp_packet) = parse_ipv4_packet(&raw_buf);
if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
info!("Connection {} reset by peer", self);
self.close();
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
if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
info!("Connection {} reset by peer", self);
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())
})
}
_ => unreachable!(),
}
}
pub fn close(&self) {
self.closing_tx.send(()).unwrap();
}
async fn accept(mut self) {
for _ in 0..RETRIES {
match self.state {
@@ -282,7 +258,7 @@ impl Drop for Socket {
if let Err(e) = self.tun.try_send(&buf) {
warn!("Unable to send RST to remote end: {}", e);
}
self.close();
info!("Fake TCP connection to {} closed", self);
}
}
@@ -396,11 +372,10 @@ impl Stack {
} else {
trace!("Cache miss, checking the shared tuples table for connection");
let sender;
{
let sender = {
let tuples = shared.tuples.read().unwrap();
sender = tuples.get(&tuple).cloned();
}
tuples.get(&tuple).cloned()
};
if let Some(c) = sender {
trace!("Storing connection information into local tuples");

View File

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

View File

@@ -6,7 +6,7 @@ use fake_tcp::{Socket, Stack};
use log::{debug, error, info};
use std::collections::HashMap;
use std::convert::TryInto;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use tokio::net::UdpSocket;
@@ -16,8 +16,17 @@ use tokio_tun::TunBuilder;
const UDP_TTL: Duration = Duration::from_secs(180);
fn new_udp_reuseport(addr: SocketAddrV4) -> UdpSocket {
let udp_sock = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::DGRAM, None).unwrap();
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();
@@ -40,7 +49,7 @@ async fn main() {
.long("local")
.required(true)
.value_name("IP:PORT")
.help("Sets the IP and port where Phantun Client listens for incoming UDP datagrams")
.help("Sets the IP and port where Phantun Client listens for incoming UDP datagrams, IPv6 address need to be specified as: \"[IPv6]:PORT\"")
.takes_value(true),
)
.arg(
@@ -48,8 +57,8 @@ async fn main() {
.short("r")
.long("remote")
.required(true)
.value_name("IP:PORT")
.help("Sets the address and port where Phantun Client connects to Phantun Server")
.value_name("IP or HOST NAME:PORT")
.help("Sets the address or host name and port where Phantun Client connects to Phantun Server")
.takes_value(true),
)
.arg(
@@ -83,16 +92,24 @@ async fn main() {
)
.get_matches();
let local_addr: SocketAddrV4 = matches
let local_addr: SocketAddr = matches
.value_of("local")
.unwrap()
.parse()
.expect("bad local address");
let remote_addr: SocketAddrV4 = matches
.value_of("remote")
.unwrap()
.parse()
.expect("bad remote address");
let remote_addr = tokio::net::lookup_host(matches.value_of("remote").unwrap())
.await
.expect("bad remote address or host")
.next()
.expect("unable to resolve remote host name");
let remote_addr = if let SocketAddr::V4(addr) = remote_addr {
addr
} else {
panic!("only IPv4 remote address is supported");
};
info!("Remote address is: {}", remote_addr);
let tun_local: Ipv4Addr = matches
.value_of("tun_local")
.unwrap()
@@ -117,7 +134,7 @@ async fn main() {
info!("Created TUN device {}", tun[0].name());
let udp_sock = Arc::new(new_udp_reuseport(local_addr));
let connections = Arc::new(RwLock::new(HashMap::<SocketAddrV4, Arc<Socket>>::new()));
let connections = Arc::new(RwLock::new(HashMap::<SocketAddr, Arc<Socket>>::new()));
let mut stack = Stack::new(tun);
@@ -126,7 +143,7 @@ async fn main() {
loop {
tokio::select! {
Ok((size, SocketAddr::V4(addr))) = udp_sock.recv_from(&mut buf_r) => {
Ok((size, addr)) = udp_sock.recv_from(&mut buf_r) => {
// seen UDP packet to listening socket, this means:
// 1. It is a new UDP connection, or
// 2. It is some extra packets not filtered by more specific

View File

@@ -4,7 +4,7 @@ use clap::{crate_version, App, Arg};
use fake_tcp::packet::MAX_PACKET_LEN;
use fake_tcp::Stack;
use log::{error, info};
use std::net::{Ipv4Addr, SocketAddrV4};
use std::net::Ipv4Addr;
use tokio::net::UdpSocket;
use tokio::time::{self, Duration};
use tokio_tun::TunBuilder;
@@ -31,8 +31,8 @@ async fn main() {
.short("r")
.long("remote")
.required(true)
.value_name("IP:PORT")
.help("Sets the address and port where Phantun Server forwards UDP packets to")
.value_name("IP or HOST NAME: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),
)
.arg(
@@ -71,11 +71,14 @@ async fn main() {
.unwrap()
.parse()
.expect("bad local port");
let remote_addr: SocketAddrV4 = matches
.value_of("remote")
.unwrap()
.parse()
.expect("bad remote address");
let remote_addr = tokio::net::lookup_host(matches.value_of("remote").unwrap())
.await
.expect("bad remote address or host")
.next()
.expect("unable to resolve remote host name");
info!("Remote address is: {}", remote_addr);
let tun_local: Ipv4Addr = matches
.value_of("tun_local")
.unwrap()
@@ -113,7 +116,13 @@ async fn main() {
info!("New connection: {}", sock);
tokio::spawn(async move {
let udp_sock = UdpSocket::bind("0.0.0.0:0").await.unwrap();
let udp_sock = UdpSocket::bind(if remote_addr.is_ipv4() {
"0.0.0.0:0"
} else {
"[::]:0"
})
.await
.unwrap();
udp_sock.connect(remote_addr).await.unwrap();
loop {