19 Commits

Author SHA1 Message Date
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
Datong Sun
cf588db042 ci(release) add musl libc targets 2021-10-19 01:13:56 -07:00
Datong Sun
0e27822995 docs(readme) update WireGuard MTU calculation to take into account for
padding truncation
2021-10-16 10:55:01 -07:00
Datong Sun
eeca1d0108 docs(readme) bump latest release version to v0.2.1 2021-10-12 07:26:49 -07:00
Datong Sun
35541df7e2 chore(phantun) bump version to v0.2.1, bump fake-tcp dependency to v0.1.2 2021-10-09 09:33:16 -07:00
Datong Sun
3219cb9f38 chore(fake-tcp) bump version to 0.1.2 2021-10-09 09:31:55 -07:00
Datong Sun
41e86521b7 fix(fake-tcp) more robust checking for receiving end closing, avoids
panicking tasks from causing server to stop completely
2021-10-10 00:29:28 +08:00
Datong Sun
427fb7c19a fix(fake-tcp) use 64 as TTL value, fixes #11 2021-10-09 23:57:47 +08:00
Datong Sun
ef96a5161d chore(phantun) release v0.2.0 2021-09-28 23:35:37 -07:00
Datong Sun
2b7588adfe docs(phantun) add -h option information 2021-09-28 23:31:07 -07:00
Datong Sun
6285efd0d7 feat(phantun) make Tun interface name and address configurable. Improved
documentations of Clap CLI help

Closes #8
2021-09-29 14:27:00 +08:00
Datong Sun
5a6ebf52ea fix(phantun) better UDP error handling
When UDP send fails, print a proper error message and close the
connection, instead of `panic`ing.
2021-09-28 00:53:52 +08:00
Datong Sun
e3e50f8a9e docs(readme) add more network topology explanation 2021-09-24 08:44:42 -07:00
Datong Sun
e97a2d1cad docs(readme) update latest release version 2021-09-22 22:23:00 -07:00
10 changed files with 201 additions and 48 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

@@ -16,12 +16,19 @@ jobs:
matrix:
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- i686-unknown-linux-gnu
- i686-unknown-linux-musl
- armv7-unknown-linux-gnueabihf
- armv7-unknown-linux-musleabihf
- arm-unknown-linux-gnueabihf
- arm-unknown-linux-musleabihf
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- mips-unknown-linux-gnu
- mips-unknown-linux-musl
- mipsel-unknown-linux-gnu
- mipsel-unknown-linux-musl
steps:
- uses: actions/checkout@v2

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,7 +31,7 @@ Table of Contents
# Latest release
[v0.1.0](https://github.com/dndx/phantun/releases/tag/v0.1.0)
[v0.2.1](https://github.com/dndx/phantun/releases/tag/v0.2.1)
# Overview
@@ -55,11 +55,39 @@ to make it pass through stateful firewall/NATs as TCP packets.
# Usage
For the example below, it is assumed that **Phantun Server** listens for incoming Phantun Client connections at
port `4567` (the `--local` option for server), and it forwards UDP packets to UDP server at `127.0.0.1:1234`
(the `--remote` option for server).
It is also assumed that **Phantun Client** listens for incoming UDP packets at
`127.0.0.1:1234` (the `--local` option for client) and connects to Phantun Server at `10.0.0.1:4567`
(the `--remote` option for client).
Phantun creates TUN interface for both the Client and Server. For Client, Phantun assigns itself the IP address
`192.168.200.2` and for Server, it assigns `192.168.201.2`. Therefore, your Kernel must have
`192.168.200.2` by default and for Server, it assigns `192.168.201.2` by default. Therefore, your Kernel must have
`net.ipv4.ip_forward` enabled and setup appropriate iptables rules for NAT between your physical
NIC address and Phantun's TUN interface address.
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.
Another way to help understand this network topology:
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
leaves the NIC.
Phantun Server is like a server with private IP address (`192.168.201.2`) behind a router.
In order to access it from the Internet, you need to `DNAT` it's listening port on the router
and change the destination IP address to where the server is listening for incoming connections.
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
@@ -96,7 +124,7 @@ table inet nat {
#### Using iptables
```
iptables -t nat -A POSTROUTING -i tun0 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
```
[Back to TOC](#table-of-contents)
@@ -146,6 +174,8 @@ sudo setcap cap_net_admin=+pe phantun_client
## 4. Start Phantun daemon
**Note:** Run Phantun executable with `-h` option to see full detailed options.
### Server
Note: `4567` is the TCP port Phantun should listen on and must corresponds to the DNAT
@@ -192,18 +222,13 @@ of obfuscation.
For people who use Phantun to tunnel [WireGuard®](https://www.wireguard.com) UDP packets, here are some guidelines on figuring
out the correct MTU to use for your WireGuard interface.
WireGuard MTU = `MAX_OF_16`(Interface MTU - IP header (20 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes))
Where:
`MAX_OF_16` takes an input integer and calculates the maximum multiple of 16 not exceeding the input. This
is needed because WireGuard will always pad it's payloads to multiple of 16 bytes.
WireGuard MTU = Interface MTU - IP header (20 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes)
For example, for a Ethernet interface with 1500 bytes MTU, the WireGuard interface MTU should be set as:
`MAX_OF_16`(1500 - 20 - 20 - 32) = 1424 bytes
1500 - 20 - 20 - 32 = 1428 bytes
The resulted Phantun TCP data packet will be 1424 + 20 + 20 + 32 = 1496 bytes which does not exceed the
The resulted Phantun TCP data packet will be 1500 bytes which does not exceed the
interface MTU of 1500.
[Back to TOC](#table-of-contents)
@@ -229,7 +254,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
@@ -257,7 +282,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.1"
edition = "2018"
version = "0.1.2"
edition = "2021"
authors = ["Datong Sun <dndx@idndx.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/dndx/phantun"

View File

@@ -385,9 +385,15 @@ impl Stack {
let tuple = AddrTuple::new(local_addr, remote_addr);
if let Some(c) = tuples.get(&tuple) {
c.send(buf).await.unwrap();
if c.send(buf).await.is_err() {
trace!("Cache hit, but receiver already closed, dropping packet");
}
continue;
// If not Ok, receiver has been closed and just fall through to the slow
// path below
} else {
trace!("Cache miss, checking the shared tuples table for connection");
let sender;

View File

@@ -32,7 +32,7 @@ pub fn build_tcp_packet(
v4.set_version(4);
v4.set_header_length(IPV4_HEADER_LEN as u8 / 4);
v4.set_next_level_protocol(ip::IpNextHeaderProtocols::Tcp);
v4.set_ttl(32);
v4.set_ttl(64);
v4.set_source(*local_addr.ip());
v4.set_destination(*remote_addr.ip());
v4.set_total_length(total_len.try_into().unwrap());

View File

@@ -1,7 +1,7 @@
[package]
name = "phantun"
version = "0.1.1"
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"
@@ -13,7 +13,7 @@ Layer 3 & Layer 4 (NAPT) firewalls/NATs.
[dependencies]
clap = "2.33.3"
socket2 = { version = "0.4.2", features = ["all"] }
fake-tcp = "0.1.1"
fake-tcp = "0.1.2"
tokio = { version = "1.12.0", features = ["full"] }
log = "0.4"
pretty_env_logger = "0.4.0"

View File

@@ -1,12 +1,12 @@
extern crate dndx_fork_tokio_tun as tokio_tun;
use clap::{App, Arg};
use clap::{crate_version, App, Arg};
use fake_tcp::packet::MAX_PACKET_LEN;
use fake_tcp::{Socket, Stack};
use log::{debug, error, info};
use std::collections::HashMap;
use std::convert::TryInto;
use std::net::{SocketAddr, SocketAddrV4};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
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();
@@ -32,15 +41,15 @@ async fn main() {
pretty_env_logger::init();
let matches = App::new("Phantun Client")
.version("1.0")
.author("dndx@GitHub")
.version(crate_version!())
.author("Datong Sun (github.com/dndx)")
.arg(
Arg::with_name("local")
.short("l")
.long("local")
.required(true)
.value_name("IP:PORT")
.help("Sets the listening socket address")
.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(
@@ -49,12 +58,41 @@ async fn main() {
.long("remote")
.required(true)
.value_name("IP:PORT")
.help("Sets the connecting socket address")
.help("Sets the address and port where Phantun Client connects to Phantun Server")
.takes_value(true),
)
.arg(
Arg::with_name("tun")
.long("tun")
.required(false)
.value_name("tunX")
.help("Sets the Tun interface name, if absent, pick the next available name")
.default_value("")
.takes_value(true),
)
.arg(
Arg::with_name("tun_local")
.long("tun-local")
.required(false)
.value_name("IP")
.help("Sets the Tun interface local address (O/S's end)")
.default_value("192.168.200.1")
.takes_value(true),
)
.arg(
Arg::with_name("tun_peer")
.long("tun-peer")
.required(false)
.value_name("IP")
.help("Sets the Tun interface destination (peer) address (Phantun Client's end). \
You will need to setup SNAT/MASQUERADE rules on your Internet facing interface \
in order for Phantun Client to connect to Phantun Server")
.default_value("192.168.200.2")
.takes_value(true),
)
.get_matches();
let local_addr: SocketAddrV4 = matches
let local_addr: SocketAddr = matches
.value_of("local")
.unwrap()
.parse()
@@ -64,21 +102,31 @@ async fn main() {
.unwrap()
.parse()
.expect("bad remote address");
let tun_local: Ipv4Addr = matches
.value_of("tun_local")
.unwrap()
.parse()
.expect("bad local address for Tun interface");
let tun_peer: Ipv4Addr = matches
.value_of("tun_peer")
.unwrap()
.parse()
.expect("bad peer address for Tun interface");
let tun = TunBuilder::new()
.name("") // 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.
.packet_info(false) // false: IFF_NO_PI, default is true.
.up() // or set it up manually using `sudo ip link set <tun-name> up`.
.address("192.168.200.1".parse().unwrap())
.destination("192.168.200.2".parse().unwrap())
.address(tun_local)
.destination(tun_peer)
.try_build_mq(num_cpus::get())
.unwrap();
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);
@@ -87,7 +135,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
@@ -139,7 +187,11 @@ async fn main() {
match res {
Some(size) => {
if size > 0 {
udp_sock.send(&buf_tcp[..size]).await.unwrap();
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 => {

View File

@@ -1,10 +1,10 @@
extern crate dndx_fork_tokio_tun as tokio_tun;
use clap::{App, Arg};
use clap::{crate_version, App, Arg};
use fake_tcp::packet::MAX_PACKET_LEN;
use fake_tcp::Stack;
use log::info;
use std::net::SocketAddrV4;
use log::{error, info};
use std::net::{Ipv4Addr, SocketAddr};
use tokio::net::UdpSocket;
use tokio::time::{self, Duration};
use tokio_tun::TunBuilder;
@@ -15,15 +15,15 @@ async fn main() {
pretty_env_logger::init();
let matches = App::new("Phantun Server")
.version("1.0")
.author("dndx@GitHub")
.version(crate_version!())
.author("Datong Sun (github.com/dndx)")
.arg(
Arg::with_name("local")
.short("l")
.long("local")
.required(true)
.value_name("PORT")
.help("Sets the listening port")
.help("Sets the port where Phantun Server listens for incoming Phantun Client TCP connections")
.takes_value(true),
)
.arg(
@@ -32,7 +32,36 @@ async fn main() {
.long("remote")
.required(true)
.value_name("IP:PORT")
.help("Sets the connecting socket address")
.help("Sets the address and port where Phantun Server forwards UDP packets to, IPv6 address need to be specified as: \"[IPv6]:PORT\"")
.takes_value(true),
)
.arg(
Arg::with_name("tun")
.long("tun")
.required(false)
.value_name("tunX")
.help("Sets the Tun interface name, if absent, pick the next available name")
.default_value("")
.takes_value(true),
)
.arg(
Arg::with_name("tun_local")
.long("tun-local")
.required(false)
.value_name("IP")
.help("Sets the Tun interface local address (O/S's end)")
.default_value("192.168.201.1")
.takes_value(true),
)
.arg(
Arg::with_name("tun_peer")
.long("tun-peer")
.required(false)
.value_name("IP")
.help("Sets the Tun interface destination (peer) address (Phantun Server's end). \
You will need to setup DNAT rules to this address in order for Phantun Server \
to accept TCP traffic from Phantun Client")
.default_value("192.168.201.2")
.takes_value(true),
)
.get_matches();
@@ -42,19 +71,29 @@ async fn main() {
.unwrap()
.parse()
.expect("bad local port");
let remote_addr: SocketAddrV4 = matches
let remote_addr: SocketAddr = matches
.value_of("remote")
.unwrap()
.parse()
.expect("bad remote address");
let tun_local: Ipv4Addr = matches
.value_of("tun_local")
.unwrap()
.parse()
.expect("bad local address for Tun interface");
let tun_peer: Ipv4Addr = matches
.value_of("tun_peer")
.unwrap()
.parse()
.expect("bad peer address for Tun interface");
let tun = TunBuilder::new()
.name("") // 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.
.packet_info(false) // false: IFF_NO_PI, default is true.
.up() // or set it up manually using `sudo ip link set <tun-name> up`.
.address("192.168.201.1".parse().unwrap())
.destination("192.168.201.2".parse().unwrap())
.address(tun_local)
.destination(tun_peer)
.try_build_mq(num_cpus::get())
.unwrap();
@@ -74,7 +113,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 {
@@ -90,7 +135,10 @@ async fn main() {
match res {
Some(size) => {
if size > 0 {
udp_sock.send(&buf_tcp[..size]).await.unwrap();
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; },