Compare commits

...

22 Commits
v0.7.0 ... main

Author SHA1 Message Date
dependabot[bot]
9bdfd76819 chore(deps): bump actions/checkout from 4 to 5
Some checks failed
Docker image build / build (push) Has been cancelled
Rust / build (push) Has been cancelled
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [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/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 22:39:04 +08:00
Datong Sun
d1c18c64f3 docs(readme): bump latest release to 0.8.1
Some checks failed
Docker image build / build (push) Has been cancelled
Rust / build (push) Has been cancelled
2025-08-22 21:02:30 -07:00
Datong Sun
b42ed82147 docs(readme): bump copyright year 2025-08-22 20:55:13 -07:00
Datong Sun
7c3864a3ed chore(cargo): bump fake-tcp to 0.7.1 and phantun to 0.8.1 2025-08-22 20:54:22 -07:00
Datong Sun
6a39e9e9d0 perf(phantun): avoid heap allocation with udp_recv_from_pktinfo() 2025-08-23 11:53:41 +08:00
Datong Sun
cedee0c699
docs(readme): release 0.8.0 and add note about MIPS build
Some checks are pending
Docker image build / build (push) Waiting to run
Rust / build (push) Waiting to run
2025-08-23 01:20:53 +08:00
Datong Sun
d969f0cc5d chore(cargo): bump fake-tcp to 0.7.0 and phantun to 0.8.0 2025-08-23 01:09:05 +08:00
WGH
19c9f2d9f2
fix(phantun): use the same source IP for UDP packet replies (#178)
This fixes an issue when Phantun may choose a source IP different from
the destination IP in the incoming packet.

Closes #177
2025-08-23 01:04:03 +08:00
Datong Sun
1252affdad
chore(github): add issue template 2025-08-23 00:34:17 +08:00
Datong Sun
141c3477f9
docs(readme): clarify the MTU calculation is on the link MTU, not interface MTU 2025-08-23 00:32:24 +08:00
Datong Sun
d8dd3e65d1
docs(readme): update copyright year 2025-08-23 00:27:23 +08:00
Datong Sun
66de44e32f
chore(cargo): bump to Rust edition 2024 (#223)
Some checks are pending
Docker image build / build (push) Waiting to run
Rust / build (push) Waiting to run
* chore(cargo): bump to Rust edition 2024 and move shared dependency into
workspace `Cargo.toml`

* style(phantun): use Rust 2024 `&&` combination for `if let`
2025-08-23 00:20:07 +08:00
Randy Li
2a37a2fc92
chore(package): add Debian and RedHat package files (#173)
---------

Signed-off-by: Randy Li <ayaka@soulik.info>
2025-08-23 00:03:21 +08:00
sshhsh
f5aac38969
chore(release): add nightly build for MIPS targets (#212)
---------

Co-authored-by: Datong Sun <dndx@idndx.com>
2025-08-22 23:50:01 +08:00
dependabot[bot]
118f20f74f chore(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [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/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-22 23:19:25 +08:00
Datong Sun
6a424fd43c chore(deps): bump dependencies to latest and fix build failure with
`neli`
2025-08-22 23:06:50 +08:00
Datong Sun
869c79422f chore(cargo): add Cargo.lock. Closes #138
Some checks failed
Docker image build / build (push) Has been cancelled
Rust / build (push) Has been cancelled
2025-03-14 22:46:01 -07:00
Datong Sun
201da45ee8 style(fake-tcp): fix warnings 2025-03-14 22:45:24 -07:00
SH Weng
333c6dd059
fix(deps): support newer version of rand crate
Closes #186.
2025-03-15 15:38:13 +08:00
Datong Sun
62f0278c1a fix(phantun): fix tokio-tun incompatiable API change
Some checks failed
Docker image build / build (push) Has been cancelled
Rust / build (push) Has been cancelled
2025-01-02 21:52:56 +08:00
Meng Zhuo
f436325d23
docs(README): fix typo
Some checks failed
Rust / build (push) Has been cancelled
2024-12-29 19:01:26 +08:00
Datong Sun
028a32d197
docs(readme): latest release is now v0.7.0
Some checks failed
Docker image build / build (push) Has been cancelled
Rust / build (push) Has been cancelled
2024-11-22 01:31:25 +08:00
31 changed files with 1910 additions and 77 deletions

15
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,15 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Operating system**
**`tcpdump` between the server and client on Phantun's port**

View File

@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup QEMU
uses: docker/setup-qemu-action@v3

View File

@ -27,7 +27,7 @@ jobs:
- aarch64-unknown-linux-musl
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
@ -50,3 +50,41 @@ jobs:
with:
files: target/${{ matrix.target }}/release/*.zip
prerelease: ${{ contains(github.ref, '-') }}
update_existing: true
build-mips-nightly:
runs-on: ubuntu-latest
env:
RUST_BACKTRACE: full
strategy:
matrix:
target:
- mips-unknown-linux-musl
- mips64-unknown-linux-muslabi64
- mipsel-unknown-linux-musl
steps:
- uses: actions/checkout@v5
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rust-src
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --target ${{ matrix.target }} -Z build-std
- name: Rename artifacts and compress
run: |
cd target/${{ matrix.target }}/release
mv client phantun_client
mv server phantun_server
zip phantun_${{ matrix.target }}_nightly.zip phantun_client phantun_server
- name: Upload Github Assets
uses: softprops/action-gh-release@v2
with:
files: target/${{ matrix.target }}/release/*.zip
prerelease: ${{ contains(github.ref, '-') }}
update_existing: true

View File

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

1
.gitignore vendored
View File

@ -1,2 +1 @@
/target
Cargo.lock

1301
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,11 @@
[workspace]
resolver = "2"
resolver = "3"
members = [
"fake-tcp",
"phantun",
]
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
log = "0"

View File

@ -5,8 +5,7 @@ A lightweight and fast UDP to TCP obfuscator.
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dndx/phantun/rust.yml)
![docs.rs](https://img.shields.io/docsrs/fake-tcp)
Table of Contents
=================
# Table of Contents
* [Phantun](#phantun)
* [Latest release](#latest-release)
@ -35,7 +34,14 @@ Table of Contents
# Latest release
[v0.6.0](https://github.com/dndx/phantun/releases/tag/v0.6.0)
[v0.8.1](https://github.com/dndx/phantun/releases/tag/v0.8.1)
<details>
<summary>MIPS architecture support for Phantun</summary>
[Rust only provides Tier 3 supports for MIPS based platforms](https://github.com/rust-lang/compiler-team/issues/648)
since 2023. Phantun's MIPS build are therefore built using nightly Rust toolchain and provided on a best effort basis only.
</details>
# Overview
@ -262,7 +268,7 @@ is the following (using IPv4 below as an example):
Note that Phantun does not add any additional header other than IP and TCP headers in order to pass through
stateful packet inspection!
Phantun's additional overhead: `12 bytes`. I other words, when using Phantun, the usable payload for
Phantun's additional overhead: `12 bytes`. In other words, when using Phantun, the usable payload for
UDP packet is reduced by 12 bytes. This is the minimum overhead possible when doing such kind
of obfuscation.
@ -276,22 +282,30 @@ For people who use Phantun to tunnel [WireGuard®](https://www.wireguard.com) UD
out the correct MTU to use for your WireGuard interface.
```
WireGuard MTU = Interface MTU - IPv4 header (20 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes)
WireGuard MTU = Link MTU - IPv4 header (20 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes)
```
or
```
WireGuard MTU = Interface MTU - IPv6 header (40 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes)
WireGuard MTU = Link MTU - IPv6 header (40 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:
For example, for a network link with 1500 bytes MTU, the WireGuard interface MTU should be set as:
IPv4: `1500 - 20 - 20 - 32 = 1428 bytes`
IPv6: `1500 - 40 - 20 - 32 = 1408 bytes`
**IPv4:** `1500 (link MTU) - 20 - 20 - 32 = 1428 bytes`
**IPv6:** `1500 (link MTU) - 40 - 20 - 32 = 1408 bytes`
The resulted Phantun TCP data packet will be 1500 bytes which does not exceed the
interface MTU of 1500. Please note it is strongly recommended to use the same interface
interface MTU of 1500.
Please note **Phantun can not function correctly if
the packet size exceeds that of the link MTU**, as Phantun do not perform any IP-fragmentation
and reassymbly. For the same reason, Phantun always sets the `DF` (Don't Fragment) bit
in the IP header to prevent intermidiate devices performing any fragmentation on the packet.
It is also *strongly recommended* to use the same interface
MTU for both ends of a WireGuard tunnel, or unexpected packet loss may occur and these issues are
generally very hard to troubleshoot.
@ -368,7 +382,7 @@ Here is a quick overview of comparison between those two to help you choose:
# License
Copyright 2021-2024 Datong Sun (dndx@idndx.com)
Copyright 2021-2025 Datong Sun (dndx@idndx.com)
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

5
debian/cargo-checksum.json vendored Normal file
View File

@ -0,0 +1,5 @@
[source.crates-io]
replace-with = "vendored-sources"
[source.vendored-sources]
directory = "vendor"

25
debian/changelog vendored Normal file
View File

@ -0,0 +1,25 @@
phantun (0.7.0) UNRELEASED; urgency=medium
[ Datong Sun ]
* fix(fake-tcp): when `connect()`-ing, attempt to get ephemeral port using algorithm similar to Linux (#162)
* chore(deps): bump dependencies to latest
* chore(cargo): bump `fake-tcp` version to `0.6.0` and `phantun` to `0.7.0`
[ dependabot[bot] ]
* chore(deps): bump docker/build-push-action from 5 to 6
* chore(release): remove MIPS targets due to being downgraded to Tier 3 support by Rust
* docs(readme): latest release is now `v0.7.0`
[ Randy Li ]
* phantun: change default tun address to link local
* phantun: add client and server xor support
* rpm: add selinux and rpm spec
* deb: add debian files
-- Randy Li <ayaka@soulik.info> Wed, 11 Dec 2024 15:30:45 +0000
phantun (0.6.1-1) UNRELEASED; urgency=medium
* Initial release. (Closes: #nnnn) <nnnn is the bug number of your ITP>
-- Randy Li <ayaka@soulik.info> Wed, 06 Nov 2024 18:58:00 +0000

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
10

19
debian/control vendored Normal file
View File

@ -0,0 +1,19 @@
Source: phantun
Section: net
Priority: optional
Maintainer: Randy Li <ayaka@soulik.info>
Build-Depends: debhelper (>= 9), cargo, rustc
Standards-Version: 4.5.0
Homepage: <insert homepage here>
Package: phantun-client
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Phantun client
Phantun client binary.
Package: phantun-server
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Phantun server
Phantun server binary.

24
debian/copyright vendored Normal file
View File

@ -0,0 +1,24 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: phantun
Source: https://github.com/hizukiayaka/phantun
Files: *
Copyright: 2023, Randy Li <ayaka@soulik.info>
License: MIT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
debian/phantun-client-wrapper vendored Normal file
View File

@ -0,0 +1,6 @@
#!/bin/bash
PID_FILE=$1
shift 1
mkdir -p /var/run/phantun
/usr/libexec/phantun/phantun-client "$@" &
echo $! > /var/run/phantun/${PID_FILE}

2
debian/phantun-client.install vendored Normal file
View File

@ -0,0 +1,2 @@
usr/libexec/phantun/phantun-client
usr/bin/phantun-client

6
debian/phantun-server-wrapper vendored Normal file
View File

@ -0,0 +1,6 @@
#!/bin/bash
PID_FILE=$1
shift 1
mkdir -p /var/run/phantun
/usr/libexec/phantun/phantun-server "$@" &
echo $! > /var/run/phantun/${PID_FILE}

2
debian/phantun-server.install vendored Normal file
View File

@ -0,0 +1,2 @@
usr/libexec/phantun/phantun-server
usr/bin/phantun-server

35
debian/rules vendored Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/make -f
%:
dh $@ --buildsystem=cargo
override_dh_auto_install:
# Define DESTDIR
DESTDIR=$(CURDIR)/debian/phantun
# Install client binary
install -D -m 0755 target/release/client debian/tmp/usr/libexec/phantun/phantun-client
# Install server binary
install -D -m 0755 target/release/server debian/tmp/usr/libexec/phantun/phantun-server
# Create wrapper scripts
install -D -m 0755 debian/phantun-client-wrapper debian/tmp/usr/bin/phantun-client
install -D -m 0755 debian/phantun-server-wrapper debian/tmp/usr/bin/phantun-server
chmod +x debian/tmp/usr/bin/phantun-client
chmod +x debian/tmp/usr/bin/phantun-server
override_dh_auto_configure:
cp ./debian/cargo-checksum.json ./.cargo-checksum.json
override_dh_auto_build:
cargo build --release
override_dh_install:
dh_install
override_dh_auto_test:
# Disable the auto test step
true

View File

@ -1,7 +1,7 @@
[package]
name = "fake-tcp"
version = "0.6.0"
edition = "2021"
version = "0.7.1"
edition = "2024"
authors = ["Datong Sun <dndx@idndx.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/dndx/phantun"
@ -17,9 +17,9 @@ benchmark = []
[dependencies]
bytes = "1"
pnet = "0"
tokio = { version = "1", features = ["full"] }
rand = { version = "0", features = ["small_rng"] }
log = "0"
internet-checksum = "0"
tokio-tun = "0"
flume = "0"
tokio = { workspace = true }
log = { workspace = true }

View File

@ -5,7 +5,7 @@ packet oriented tunneling with minimum overhead.
## License
Copyright 2021 Datong Sun <dndx@idndx.com>
Copyright 2021-2025 Datong Sun <dndx@idndx.com>
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

View File

@ -402,8 +402,8 @@ impl Stack {
/// Connects to the remote end. `None` returned means
/// the connection attempt failed.
pub async fn connect(&mut self, addr: SocketAddr) -> Option<Socket> {
let mut rng = SmallRng::from_entropy();
for local_port in rng.gen_range(32768..=60999)..=60999 {
let mut rng = SmallRng::from_os_rng();
for local_port in rng.random_range(32768..=60999)..=60999 {
let local_addr = SocketAddr::new(
if addr.is_ipv4() {
IpAddr::V4(self.local_ip)

View File

@ -15,7 +15,7 @@ pub enum IPPacket<'p> {
V6(ipv6::Ipv6Packet<'p>),
}
impl<'a> IPPacket<'a> {
impl IPPacket<'_> {
pub fn get_source(&self) -> IpAddr {
match self {
IPPacket::V4(p) => IpAddr::V4(p.get_source()),
@ -127,7 +127,7 @@ pub fn build_tcp_packet(
ip_buf.freeze()
}
pub fn parse_ip_packet(buf: &Bytes) -> Option<(IPPacket, tcp::TcpPacket)> {
pub fn parse_ip_packet(buf: &Bytes) -> Option<(IPPacket<'_>, tcp::TcpPacket<'_>)> {
if buf[0] >> 4 == 4 {
let v4 = ipv4::Ipv4Packet::new(buf).unwrap();
if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp {

View File

@ -1,7 +1,7 @@
[package]
name = "phantun"
version = "0.7.0"
edition = "2021"
version = "0.8.1"
edition = "2024"
authors = ["Datong Sun <dndx@idndx.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/dndx/phantun"
@ -14,11 +14,11 @@ Layer 3 & Layer 4 (NAPT) firewalls/NATs.
clap = { version = "4", features = ["cargo"] }
socket2 = { version = "0", features = ["all"] }
fake-tcp = { path = "../fake-tcp", version = "0" }
tokio = { version = "1", features = ["full"] }
tokio-util = "0"
log = "0"
pretty_env_logger = "0"
tokio-tun = "0"
num_cpus = "1"
neli = "0"
nix = { version = "0", features = ["net"] }
nix = { version = "0", features = ["net", "uio", "socket"] }
tokio = { workspace = true }
log = { workspace = true }

View File

@ -4,7 +4,7 @@ Client/Server crate, see [Phantun Project README.md](https://github.com/dndx/pha
## License
Copyright 2021 Datong Sun <dndx@idndx.com>
Copyright 2021-2025 Datong Sun <dndx@idndx.com>
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

View File

@ -2,11 +2,11 @@ use clap::{crate_version, Arg, ArgAction, Command};
use fake_tcp::packet::MAX_PACKET_LEN;
use fake_tcp::{Socket, Stack};
use log::{debug, error, info};
use phantun::utils::{assign_ipv6_address, new_udp_reuseport};
use phantun::utils::{assign_ipv6_address, new_udp_reuseport, udp_recv_pktinfo};
use std::collections::HashMap;
use std::fs;
use std::io;
use std::net::{Ipv4Addr, SocketAddr};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::sync::Arc;
use tokio::sync::{Notify, RwLock};
use tokio::time;
@ -156,7 +156,8 @@ async fn main() -> io::Result<()> {
.up() // or set it up manually using `sudo ip link set <tun-name> up`.
.address(tun_local)
.destination(tun_peer)
.try_build_mq(num_cpus)
.queues(num_cpus)
.build()
.unwrap();
if remote_addr.is_ipv6() {
@ -174,17 +175,17 @@ async fn main() -> io::Result<()> {
let mut buf_r = [0u8; MAX_PACKET_LEN];
loop {
let (size, addr) = udp_sock.recv_from(&mut buf_r).await?;
let (size, udp_remote_addr, udp_local_addr) = udp_recv_pktinfo(&udp_sock, &mut buf_r).await?;
// 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
// connected UDP socket yet
if let Some(sock) = connections.read().await.get(&addr) {
if let Some(sock) = connections.read().await.get(&udp_remote_addr) {
sock.send(&buf_r[..size]).await;
continue;
}
info!("New UDP client from {}", addr);
info!("New UDP client from {}", udp_remote_addr);
let sock = stack.connect(remote_addr).await;
if sock.is_none() {
error!("Unable to connect to remote {}", remote_addr);
@ -209,7 +210,7 @@ async fn main() -> io::Result<()> {
assert!(connections
.write()
.await
.insert(addr, sock.clone())
.insert(udp_remote_addr, sock.clone())
.is_none());
debug!("inserted fake TCP socket into connection table");
@ -227,8 +228,34 @@ async fn main() -> io::Result<()> {
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();
// Always reply from the same address that the peer used to communicate with
// us. This avoids a frequent problem with IPv6 privacy extensions when we
// erroneously bind to wrong short-lived temporary address even if the peer
// explicitly used a persistent address to communicate to us.
//
// To do so, first bind to (<incoming packet dst_ip>, <local addr port>), and then
// connect to (<incoming packet src_ip>, <incoming packet src_port>).
let bind_addr = match (udp_remote_addr, udp_local_addr) {
(SocketAddr::V4(_), IpAddr::V4(udp_local_ipv4)) => {
SocketAddr::V4(SocketAddrV4::new(
udp_local_ipv4,
local_addr.port(),
))
}
(SocketAddr::V6(udp_remote_addr), IpAddr::V6(udp_local_ipv6)) => {
SocketAddr::V6(SocketAddrV6::new(
udp_local_ipv6,
local_addr.port(),
udp_remote_addr.flowinfo(),
udp_remote_addr.scope_id(),
))
}
(_, _) => {
panic!("unexpected family combination for udp_remote_addr={udp_remote_addr} and udp_local_addr={udp_local_addr}");
}
};
let udp_sock = new_udp_reuseport(bind_addr);
udp_sock.connect(udp_remote_addr).await.unwrap();
loop {
tokio::select! {
@ -244,13 +271,12 @@ async fn main() -> io::Result<()> {
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);
if size > 0
&& 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 => {
debug!("removed fake TCP socket from connections table");
@ -279,14 +305,14 @@ async fn main() -> io::Result<()> {
tokio::select! {
_ = read_timeout => {
info!("No traffic seen in the last {:?}, closing connection", UDP_TTL);
connections.write().await.remove(&addr);
connections.write().await.remove(&udp_remote_addr);
debug!("removed fake TCP socket from connections table");
quit.cancel();
return;
},
_ = quit.cancelled() => {
connections.write().await.remove(&addr);
connections.write().await.remove(&udp_remote_addr);
debug!("removed fake TCP socket from connections table");
return;
},

View File

@ -155,7 +155,8 @@ async fn main() -> io::Result<()> {
.up() // or set it up manually using `sudo ip link set <tun-name> up`.
.address(tun_local)
.destination(tun_peer)
.try_build_mq(num_cpus)
.queues(num_cpus)
.build()
.unwrap();
if let (Some(tun_local6), Some(tun_peer6)) = (tun_local6, tun_peer6) {
@ -218,13 +219,12 @@ async fn main() -> io::Result<()> {
res = sock.recv(&mut buf_tcp) => {
match res {
Some(size) => {
if size > 0 {
if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
if size > 0
&& 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();

View File

@ -1,15 +1,21 @@
use neli::{
consts::{
nl::{NlmF, NlmFFlags},
rtnl::{Ifa, IfaFFlags, RtAddrFamily, Rtm},
nl::NlmF,
rtnl::{Ifa, IfaF, RtAddrFamily, RtScope, Rtm},
socket::NlFamily,
},
nl::{NlPayload, Nlmsghdr},
rtnl::{Ifaddrmsg, Rtattr},
socket::NlSocketHandle,
nl::{NlPayload, NlmsghdrBuilder},
rtnl::{IfaddrmsgBuilder, RtattrBuilder},
socket::synchronous::NlSocketHandle,
types::RtBuffer,
utils::Groups,
};
use std::net::{Ipv6Addr, SocketAddr};
use nix::sys::socket::{
CmsgIterator, ControlMessageOwned, MsgFlags, SockaddrLike, SockaddrStorage, cmsg_space,
};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::os::unix::io::AsRawFd;
use tokio::io::Interest;
use tokio::net::UdpSocket;
pub fn new_udp_reuseport(local_addr: SocketAddr) -> UdpSocket {
@ -27,34 +33,121 @@ pub fn new_udp_reuseport(local_addr: SocketAddr) -> UdpSocket {
// from tokio-rs/mio/blob/master/src/sys/unix/net.rs
udp_sock.set_cloexec(true).unwrap();
udp_sock.set_nonblocking(true).unwrap();
// enable IP_PKTINFO/IPV6_PKTINFO delivery so we know the destination address of incoming
// packets
if local_addr.is_ipv4() {
nix::sys::socket::setsockopt(&udp_sock, nix::sys::socket::sockopt::Ipv4PacketInfo, &true)
.unwrap();
} else {
nix::sys::socket::setsockopt(
&udp_sock,
nix::sys::socket::sockopt::Ipv6RecvPacketInfo,
&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()
}
/// Similiar to `UdpSocket::recv_from()`, but returns a 3rd value `IPAddr`
/// which corresponds to where the UDP datagram was destined to, this is useful
/// for disambigous when socket can receive on multiple IP address
/// or interfaces.
pub async fn udp_recv_pktinfo(
sock: &UdpSocket,
buf: &mut [u8],
) -> std::io::Result<(usize, SocketAddr, IpAddr)> {
sock.async_io(Interest::READABLE, || {
// according to documented struct definition in RFC 3542,
// sizeof(in6_pktinfo) should always be larger than sizeof(in_pktinfo),
// this assert just double checks that. The goal is to avoid
// a heap allocation with Vec at runtime.
assert!(cmsg_space::<nix::libc::in6_pktinfo>() >= cmsg_space::<nix::libc::in_pktinfo>());
let mut control_message_buffer = [0u8; cmsg_space::<nix::libc::in6_pktinfo>()];
let iov = &mut [std::io::IoSliceMut::new(buf)];
let res = nix::sys::socket::recvmsg::<SockaddrStorage>(
sock.as_raw_fd(),
iov,
Some(&mut control_message_buffer),
MsgFlags::empty(),
)?;
let src_addr = res.address.expect("missing source address");
let src_addr: SocketAddr = {
if let Some(inaddr) = src_addr.as_sockaddr_in() {
SocketAddrV4::new(inaddr.ip(), inaddr.port()).into()
} else if let Some(in6addr) = src_addr.as_sockaddr_in6() {
SocketAddrV6::new(
in6addr.ip(),
in6addr.port(),
in6addr.flowinfo(),
in6addr.scope_id(),
)
.into()
} else {
panic!("unexpected source address family {:#?}", src_addr.family());
}
};
let dst_addr = dst_addr_from_cmsgs(res.cmsgs()?).expect("didn't receive pktinfo");
Ok((res.bytes, src_addr, dst_addr))
})
.await
}
fn dst_addr_from_cmsgs(cmsgs: CmsgIterator) -> Option<IpAddr> {
for cmsg in cmsgs {
if let ControlMessageOwned::Ipv4PacketInfo(pktinfo) = cmsg {
return Some(Ipv4Addr::from(pktinfo.ipi_addr.s_addr.to_ne_bytes()).into());
}
if let ControlMessageOwned::Ipv6PacketInfo(pktinfo) = cmsg {
return Some(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr).into());
}
}
None
}
pub fn assign_ipv6_address(device_name: &str, local: Ipv6Addr, peer: Ipv6Addr) {
let index = nix::net::if_::if_nametoindex(device_name).unwrap();
let mut rtnl = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap();
let rtnl = NlSocketHandle::connect(NlFamily::Route, None, Groups::empty()).unwrap();
let mut rtattrs = RtBuffer::new();
rtattrs.push(Rtattr::new(None, Ifa::Local, &local.octets()[..]).unwrap());
rtattrs.push(Rtattr::new(None, Ifa::Address, &peer.octets()[..]).unwrap());
let ifaddrmsg = Ifaddrmsg {
ifa_family: RtAddrFamily::Inet6,
ifa_prefixlen: 128,
ifa_flags: IfaFFlags::empty(),
ifa_scope: 0,
ifa_index: index as i32,
rtattrs,
};
let nl_header = Nlmsghdr::new(
None,
Rtm::Newaddr,
NlmFFlags::new(&[NlmF::Request]),
None,
None,
NlPayload::Payload(ifaddrmsg),
rtattrs.push(
RtattrBuilder::default()
.rta_type(Ifa::Local)
.rta_payload(&local.octets()[..])
.build()
.unwrap(),
);
rtnl.send(nl_header).unwrap();
rtattrs.push(
RtattrBuilder::default()
.rta_type(Ifa::Address)
.rta_payload(&peer.octets()[..])
.build()
.unwrap(),
);
let ifaddrmsg = IfaddrmsgBuilder::default()
.ifa_family(RtAddrFamily::Inet6)
.ifa_prefixlen(128)
.ifa_flags(IfaF::empty())
.ifa_scope(RtScope::Universe)
.ifa_index(index)
.rtattrs(rtattrs)
.build()
.unwrap();
let nl_header = NlmsghdrBuilder::default()
.nl_type(Rtm::Newaddr)
.nl_flags(NlmF::REQUEST)
.nl_payload(NlPayload::Payload(ifaddrmsg))
.build()
.unwrap();
rtnl.send(&nl_header).unwrap();
}

128
rpm/phantun.spec Normal file
View File

@ -0,0 +1,128 @@
Name: phantun
Version: 0.7.0
Release: 2%{?dist}
Summary: A lightweight and fast UDP to TCP obfuscator
License: Apache-2.0
URL: https://github.com/dndx/phantun/tree/main
Source0: %{name}-%{version}.tar.gz
BuildRequires: rust
BuildRequires: cargo
BuildRequires: selinux-policy-devel
%description
Your project with client and server components.
%package client
Summary: Client component of phantun
Requires: (%{name}-selinux if selinux-policy-%{selinuxtype})
%description client
Phantun Client is like a machine with private IP address
(192.168.200.2/fcc8::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.
%package server
Summary: Server component of phantun
Requires: (%{name}-selinux if selinux-policy-%{selinuxtype})
%description server
Phantun Server is like a server with private IP address
(192.168.201.2/fcc9::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.
%package selinux
Summary: SELinux module for phantun
%{?selinux_requires}
%global modulename phantun
%global selinuxtype targeted
%description selinux
This package provides the SELinux policy module to ensure phantun
runs properly under an environment with SELinux enabled.
%global debug_package %{nil}
%prep
%setup -q
%build
cargo build --release
make -C selinux
%install
# Install binaries
install -D -m 0755 target/release/client %{buildroot}/usr/libexec/phantun/phantun-client
install -D -m 0755 target/release/server %{buildroot}/usr/libexec/phantun/phantun-server
mkdir -p %{buildroot}/usr/bin
# Create wrapper scripts
echo '#!/bin/bash
PID_FILE=$1
shift 1
mkdir -p /var/run/phantun
/usr/libexec/phantun/phantun-client "$@" &
echo $! > /var/run/phantun/${PID_FILE}' > %{buildroot}/usr/bin/phantun-client
echo '#!/bin/bash
PID_FILE=$1
shift 1
mkdir -p /var/run/phantun
/usr/libexec/phantun/phantun-server "$@" &
echo $! > /var/run/phantun/${PID_FILE}' > %{buildroot}/usr/bin/phantun-server
# Make wrapper scripts executable
chmod +x %{buildroot}/usr/bin/phantun-client
chmod +x %{buildroot}/usr/bin/phantun-server
# SELinux
install -d %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}
install -m 0644 selinux/%{modulename}.pp.bz2 %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}
%pre selinux
%selinux_relabel_pre -s %{selinuxtype}
%post selinux
%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%postun selinux
if [ $1 -eq 0 ]; then
%selinux_modules_uninstall -s %{selinuxtype} %{modulename}
fi
%posttrans selinux
%selinux_relabel_post -s %{selinuxtype}
%files client
/usr/libexec/phantun/phantun-client
/usr/bin/phantun-client
%files server
/usr/libexec/phantun/phantun-server
/usr/bin/phantun-server
%files selinux
%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2
%changelog
* Wed Dec 11 2024 Randy Li <ayaka@soulik.info> - 0.7.0-2
- chore(deps): update tokio-tun requirement from 0.9 to 0.11
- chore(deps): update nix requirement from 0.27 to 0.28
- chore(deps): bump softprops/action-gh-release from 1 to 2
- chore(docs): update license year to 2024
- docs(readme): update `README.md` to include incoming interface (`-i tun0`) in client NAT commands example (#163)
- Revert "docs(readme): update `README.md` to include incoming interface (`-i t"
- fix(fake-tcp): when `connect()`-ing, attempt to get ephemeral port using algorithm similar to Linux (#162)
- chore(deps): bump dependencies to latest
- chore(cargo): bump `fake-tcp` version to `0.6.0` and `phantun` to `0.7.0`
- chore(deps): bump docker/build-push-action from 5 to 6
- chore(release): remove MIPS targets due to being downgraded to Tier 3 support by Rust
- docs(readme): latest release is now `v0.7.0`
* Sat Oct 14 2023 Randy Li <ayaka@soulik.info> - 0.6.1-1
- Initial package

26
selinux/Makefile Normal file
View File

@ -0,0 +1,26 @@
TARGET?=phantun
MODULES?=${TARGET:=.pp.bz2}
SHAREDIR?=/usr/share
all: ${TARGET:=.pp.bz2}
%.pp.bz2: %.pp
@echo Compressing $^ -\> $@
bzip2 -9 $^
%.pp: %.te
make -f ${SHAREDIR}/selinux/devel/Makefile $@
clean:
rm -f *~ *.tc *.pp *.pp.bz2
rm -rf tmp *.tar.gz
man: install-policy
sepolicy manpage --path . --domain ${TARGET}_t
install-policy: all
semodule -i ${TARGET}.pp.bz2
install: man
install -D -m 644 ${TARGET}.pp.bz2 ${DESTDIR}${SHAREDIR}/selinux/packages/${TARGET}.pp.bz2
install -D -m 644 ${TARGET}_selinux.8 ${DESTDIR}${SHAREDIR}/man/man8/

5
selinux/phantun.fc Normal file
View File

@ -0,0 +1,5 @@
/usr/libexec/phantun/phantun-client -- gen_context(system_u:object_r:phantun_client_exec_t,s0)
/usr/libexec/phantun/phantun-server -- gen_context(system_u:object_r:phantun_server_exec_t,s0)
/usr/bin/phantun-client -- gen_context(system_u:object_r:wireguard_exec_t,s0)
/usr/bin/phantun-server -- gen_context(system_u:object_r:wireguard_exec_t,s0)
/var/run/phantun(/.*)? gen_context(system_u:object_r:phantun_var_run_t,s0)

60
selinux/phantun.te Normal file
View File

@ -0,0 +1,60 @@
policy_module(phantun, 1.0)
gen_require(`
type wireguard_t;
type wireguard_exec_t;
class capability net_admin;
class tun_socket { append bind connect create getattr getopt ioctl lock read relabelfrom relabelto setattr setopt shutdown write };
class tcp_socket { name_bind listen accept connect };
class udp_socket { name_bind };
class file { getattr open read write create unlink execute };
class process { transition };
')
# Define custom types
type phantun_server_exec_t;
type phantun_client_exec_t;
type phantun_server_port_t;
type phantun_client_port_t;
type phantun_var_run_t;
# Allow the wrapper scripts to execute the phantun client and server binaries
allow wireguard_exec_t phantun_client_exec_t:file { getattr open read execute };
allow wireguard_exec_t phantun_server_exec_t:file { getattr open read execute };
# Allow the wrapper scripts to write to the PID file
allow wireguard_exec_t phantun_var_run_t:file { getattr open read write create unlink };
allow wireguard_t self:process transition;
####################################
# Server
#
# Allow wireguard_t to execute the server binary
allow wireguard_t phantun_server_exec_t:file { getattr open read execute };
# Allow the server to create and manage tun devices
allow phantun_server_exec_t self:tun_socket { append bind connect create getattr getopt ioctl lock read relabelfrom relabelto setattr setopt shutdown write };
# Allow the server to bind to the custom TCP port and listen for incoming connections
allow phantun_server_exec_t phantun_server_port_t:tcp_socket { name_bind listen accept };
# Allow the server to use net_admin capability
allow phantun_server_exec_t self:capability net_admin;
####################################
# Client
#
# Allow wireguard_t to execute the client binary
allow wireguard_t phantun_client_exec_t:file { getattr open read execute };
# Allow the client to create and manage tun devices
allow phantun_client_exec_t self:tun_socket { append bind connect create getattr getopt ioctl lock read relabelfrom relabelto setattr setopt shutdown write };
# Allow the client to bind to the custom UDP port
#allow phantun_client_exec_t phantun_client_port_t:udp_socket { name_bind };
# Allow the client to use net_admin capability
allow phantun_client_exec_t self:capability net_admin;