mirror of
https://github.com/dndx/phantun.git
synced 2025-09-16 04:04:29 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
494abf37c5 | ||
|
cab87bd75b | ||
|
042f5af49f | ||
|
f667f56747 | ||
|
49665b906f | ||
|
e9cde27923 | ||
|
cf588db042 | ||
|
0e27822995 | ||
|
eeca1d0108 | ||
|
35541df7e2 | ||
|
3219cb9f38 | ||
|
41e86521b7 | ||
|
427fb7c19a | ||
|
ef96a5161d | ||
|
2b7588adfe | ||
|
6285efd0d7 | ||
|
5a6ebf52ea | ||
|
e3e50f8a9e | ||
|
e97a2d1cad |
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
3
.github/workflows/rust.yml
vendored
3
.github/workflows/rust.yml
vendored
@@ -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
|
||||
|
51
README.md
51
README.md
@@ -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)
|
||||
|
||||
|
@@ -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"
|
||||
|
@@ -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;
|
||||
|
@@ -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());
|
||||
|
@@ -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"
|
||||
|
@@ -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 => {
|
||||
|
@@ -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; },
|
||||
|
Reference in New Issue
Block a user