mirror of
https://github.com/dndx/phantun.git
synced 2025-09-16 12:14:29 +08:00
Compare commits
23 Commits
v0.7.0
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
|
8868960830 | ||
|
9bdfd76819 | ||
|
d1c18c64f3 | ||
|
b42ed82147 | ||
|
7c3864a3ed | ||
|
6a39e9e9d0 | ||
|
cedee0c699 | ||
|
d969f0cc5d | ||
|
19c9f2d9f2 | ||
|
1252affdad | ||
|
141c3477f9 | ||
|
d8dd3e65d1 | ||
|
66de44e32f | ||
|
2a37a2fc92 | ||
|
f5aac38969 | ||
|
118f20f74f | ||
|
6a424fd43c | ||
|
869c79422f | ||
|
201da45ee8 | ||
|
333c6dd059 | ||
|
62f0278c1a | ||
|
f436325d23 | ||
|
028a32d197 |
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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**
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
40
.github/workflows/release.yml
vendored
40
.github/workflows/release.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- aarch64-unknown-linux-musl
|
- aarch64-unknown-linux-musl
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
@@ -50,3 +50,41 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: target/${{ matrix.target }}/release/*.zip
|
files: target/${{ matrix.target }}/release/*.zip
|
||||||
prerelease: ${{ contains(github.ref, '-') }}
|
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
|
||||||
|
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
/target
|
/target
|
||||||
Cargo.lock
|
|
||||||
|
1301
Cargo.lock
generated
Normal file
1301
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,11 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
resolver = "2"
|
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"fake-tcp",
|
"fake-tcp",
|
||||||
"phantun",
|
"phantun",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
log = "0"
|
||||||
|
36
README.md
36
README.md
@@ -5,8 +5,7 @@ A lightweight and fast UDP to TCP obfuscator.
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
Table of Contents
|
# Table of Contents
|
||||||
=================
|
|
||||||
|
|
||||||
* [Phantun](#phantun)
|
* [Phantun](#phantun)
|
||||||
* [Latest release](#latest-release)
|
* [Latest release](#latest-release)
|
||||||
@@ -35,7 +34,14 @@ Table of Contents
|
|||||||
|
|
||||||
# Latest release
|
# 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
|
# 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
|
Note that Phantun does not add any additional header other than IP and TCP headers in order to pass through
|
||||||
stateful packet inspection!
|
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
|
UDP packet is reduced by 12 bytes. This is the minimum overhead possible when doing such kind
|
||||||
of obfuscation.
|
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.
|
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
|
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`
|
**IPv4:** `1500 (link MTU) - 20 - 20 - 32 = 1428 bytes`
|
||||||
IPv6: `1500 - 40 - 20 - 32 = 1408 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
|
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
|
MTU for both ends of a WireGuard tunnel, or unexpected packet loss may occur and these issues are
|
||||||
generally very hard to troubleshoot.
|
generally very hard to troubleshoot.
|
||||||
|
|
||||||
@@ -368,7 +382,7 @@ Here is a quick overview of comparison between those two to help you choose:
|
|||||||
|
|
||||||
# License
|
# 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
|
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
|
||||||
|
5
debian/cargo-checksum.json
vendored
Normal file
5
debian/cargo-checksum.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[source.crates-io]
|
||||||
|
replace-with = "vendored-sources"
|
||||||
|
|
||||||
|
[source.vendored-sources]
|
||||||
|
directory = "vendor"
|
25
debian/changelog
vendored
Normal file
25
debian/changelog
vendored
Normal 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
1
debian/compat
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
10
|
19
debian/control
vendored
Normal file
19
debian/control
vendored
Normal 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
24
debian/copyright
vendored
Normal 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
6
debian/phantun-client-wrapper
vendored
Normal 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
2
debian/phantun-client.install
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
usr/libexec/phantun/phantun-client
|
||||||
|
usr/bin/phantun-client
|
6
debian/phantun-server-wrapper
vendored
Normal file
6
debian/phantun-server-wrapper
vendored
Normal 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
2
debian/phantun-server.install
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
usr/libexec/phantun/phantun-server
|
||||||
|
usr/bin/phantun-server
|
35
debian/rules
vendored
Executable file
35
debian/rules
vendored
Executable 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
|
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fake-tcp"
|
name = "fake-tcp"
|
||||||
version = "0.6.0"
|
version = "0.7.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
authors = ["Datong Sun <dndx@idndx.com>"]
|
authors = ["Datong Sun <dndx@idndx.com>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/dndx/phantun"
|
repository = "https://github.com/dndx/phantun"
|
||||||
@@ -17,9 +17,9 @@ benchmark = []
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
pnet = "0"
|
pnet = "0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
rand = { version = "0", features = ["small_rng"] }
|
rand = { version = "0", features = ["small_rng"] }
|
||||||
log = "0"
|
|
||||||
internet-checksum = "0"
|
internet-checksum = "0"
|
||||||
tokio-tun = "0"
|
tokio-tun = "0"
|
||||||
flume = "0"
|
flume = "0"
|
||||||
|
tokio = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
@@ -5,7 +5,7 @@ packet oriented tunneling with minimum overhead.
|
|||||||
|
|
||||||
## License
|
## 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
|
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
|
||||||
|
@@ -402,8 +402,8 @@ impl Stack {
|
|||||||
/// Connects to the remote end. `None` returned means
|
/// Connects to the remote end. `None` returned means
|
||||||
/// the connection attempt failed.
|
/// the connection attempt failed.
|
||||||
pub async fn connect(&mut self, addr: SocketAddr) -> Option<Socket> {
|
pub async fn connect(&mut self, addr: SocketAddr) -> Option<Socket> {
|
||||||
let mut rng = SmallRng::from_entropy();
|
let mut rng = SmallRng::from_os_rng();
|
||||||
for local_port in rng.gen_range(32768..=60999)..=60999 {
|
for local_port in rng.random_range(32768..=60999)..=60999 {
|
||||||
let local_addr = SocketAddr::new(
|
let local_addr = SocketAddr::new(
|
||||||
if addr.is_ipv4() {
|
if addr.is_ipv4() {
|
||||||
IpAddr::V4(self.local_ip)
|
IpAddr::V4(self.local_ip)
|
||||||
|
@@ -15,7 +15,7 @@ pub enum IPPacket<'p> {
|
|||||||
V6(ipv6::Ipv6Packet<'p>),
|
V6(ipv6::Ipv6Packet<'p>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IPPacket<'a> {
|
impl IPPacket<'_> {
|
||||||
pub fn get_source(&self) -> IpAddr {
|
pub fn get_source(&self) -> IpAddr {
|
||||||
match self {
|
match self {
|
||||||
IPPacket::V4(p) => IpAddr::V4(p.get_source()),
|
IPPacket::V4(p) => IpAddr::V4(p.get_source()),
|
||||||
@@ -127,7 +127,7 @@ pub fn build_tcp_packet(
|
|||||||
ip_buf.freeze()
|
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 {
|
if buf[0] >> 4 == 4 {
|
||||||
let v4 = ipv4::Ipv4Packet::new(buf).unwrap();
|
let v4 = ipv4::Ipv4Packet::new(buf).unwrap();
|
||||||
if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp {
|
if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "phantun"
|
name = "phantun"
|
||||||
version = "0.7.0"
|
version = "0.8.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
authors = ["Datong Sun <dndx@idndx.com>"]
|
authors = ["Datong Sun <dndx@idndx.com>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/dndx/phantun"
|
repository = "https://github.com/dndx/phantun"
|
||||||
@@ -14,11 +14,11 @@ Layer 3 & Layer 4 (NAPT) firewalls/NATs.
|
|||||||
clap = { version = "4", features = ["cargo"] }
|
clap = { version = "4", features = ["cargo"] }
|
||||||
socket2 = { version = "0", features = ["all"] }
|
socket2 = { version = "0", features = ["all"] }
|
||||||
fake-tcp = { path = "../fake-tcp", version = "0" }
|
fake-tcp = { path = "../fake-tcp", version = "0" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
tokio-util = "0"
|
tokio-util = "0"
|
||||||
log = "0"
|
|
||||||
pretty_env_logger = "0"
|
pretty_env_logger = "0"
|
||||||
tokio-tun = "0"
|
tokio-tun = "0"
|
||||||
num_cpus = "1"
|
num_cpus = "1"
|
||||||
neli = "0"
|
neli = "0"
|
||||||
nix = { version = "0", features = ["net"] }
|
nix = { version = "0", features = ["net", "uio", "socket"] }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
@@ -4,7 +4,7 @@ Client/Server crate, see [Phantun Project README.md](https://github.com/dndx/pha
|
|||||||
|
|
||||||
## License
|
## 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
|
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
|
||||||
|
@@ -2,11 +2,11 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
|||||||
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::{assign_ipv6_address, new_udp_reuseport};
|
use phantun::utils::{assign_ipv6_address, new_udp_reuseport, udp_recv_pktinfo};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::{Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{Notify, RwLock};
|
use tokio::sync::{Notify, RwLock};
|
||||||
use tokio::time;
|
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`.
|
.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)
|
.queues(num_cpus)
|
||||||
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if remote_addr.is_ipv6() {
|
if remote_addr.is_ipv6() {
|
||||||
@@ -174,17 +175,17 @@ async fn main() -> io::Result<()> {
|
|||||||
let mut buf_r = [0u8; MAX_PACKET_LEN];
|
let mut buf_r = [0u8; MAX_PACKET_LEN];
|
||||||
|
|
||||||
loop {
|
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:
|
// seen UDP packet to listening socket, this means:
|
||||||
// 1. It is a new UDP connection, or
|
// 1. It is a new UDP connection, or
|
||||||
// 2. It is some extra packets not filtered by more specific
|
// 2. It is some extra packets not filtered by more specific
|
||||||
// connected UDP socket yet
|
// 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;
|
sock.send(&buf_r[..size]).await;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("New UDP client from {}", addr);
|
info!("New UDP client from {}", udp_remote_addr);
|
||||||
let sock = stack.connect(remote_addr).await;
|
let sock = stack.connect(remote_addr).await;
|
||||||
if sock.is_none() {
|
if sock.is_none() {
|
||||||
error!("Unable to connect to remote {}", remote_addr);
|
error!("Unable to connect to remote {}", remote_addr);
|
||||||
@@ -209,7 +210,7 @@ async fn main() -> io::Result<()> {
|
|||||||
assert!(connections
|
assert!(connections
|
||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.insert(addr, sock.clone())
|
.insert(udp_remote_addr, sock.clone())
|
||||||
.is_none());
|
.is_none());
|
||||||
debug!("inserted fake TCP socket into connection table");
|
debug!("inserted fake TCP socket into connection table");
|
||||||
|
|
||||||
@@ -227,8 +228,34 @@ async fn main() -> io::Result<()> {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf_udp = [0u8; MAX_PACKET_LEN];
|
let mut buf_udp = [0u8; MAX_PACKET_LEN];
|
||||||
let mut buf_tcp = [0u8; MAX_PACKET_LEN];
|
let mut buf_tcp = [0u8; MAX_PACKET_LEN];
|
||||||
let udp_sock = new_udp_reuseport(local_addr);
|
// Always reply from the same address that the peer used to communicate with
|
||||||
udp_sock.connect(addr).await.unwrap();
|
// 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 {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -244,13 +271,12 @@ async fn main() -> io::Result<()> {
|
|||||||
res = sock.recv(&mut buf_tcp) => {
|
res = sock.recv(&mut buf_tcp) => {
|
||||||
match res {
|
match res {
|
||||||
Some(size) => {
|
Some(size) => {
|
||||||
if size > 0 {
|
if size > 0
|
||||||
if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
|
&& let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
|
||||||
error!("Unable to send UDP packet to {}: {}, closing connection", e, addr);
|
error!("Unable to send UDP packet to {}: {}, closing connection", e, remote_addr);
|
||||||
quit.cancel();
|
quit.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
debug!("removed fake TCP socket from connections table");
|
debug!("removed fake TCP socket from connections table");
|
||||||
@@ -279,14 +305,14 @@ async fn main() -> io::Result<()> {
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = 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(&udp_remote_addr);
|
||||||
debug!("removed fake TCP socket from connections table");
|
debug!("removed fake TCP socket from connections table");
|
||||||
|
|
||||||
quit.cancel();
|
quit.cancel();
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
_ = quit.cancelled() => {
|
_ = quit.cancelled() => {
|
||||||
connections.write().await.remove(&addr);
|
connections.write().await.remove(&udp_remote_addr);
|
||||||
debug!("removed fake TCP socket from connections table");
|
debug!("removed fake TCP socket from connections table");
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
@@ -155,7 +155,8 @@ async fn main() -> io::Result<()> {
|
|||||||
.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)
|
.queues(num_cpus)
|
||||||
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if let (Some(tun_local6), Some(tun_peer6)) = (tun_local6, tun_peer6) {
|
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) => {
|
res = sock.recv(&mut buf_tcp) => {
|
||||||
match res {
|
match res {
|
||||||
Some(size) => {
|
Some(size) => {
|
||||||
if size > 0 {
|
if size > 0
|
||||||
if let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
|
&& let Err(e) = udp_sock.send(&buf_tcp[..size]).await {
|
||||||
error!("Unable to send UDP packet to {}: {}, closing connection", e, remote_addr);
|
error!("Unable to send UDP packet to {}: {}, closing connection", e, remote_addr);
|
||||||
quit.cancel();
|
quit.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
quit.cancel();
|
quit.cancel();
|
||||||
|
@@ -1,15 +1,21 @@
|
|||||||
use neli::{
|
use neli::{
|
||||||
consts::{
|
consts::{
|
||||||
nl::{NlmF, NlmFFlags},
|
nl::NlmF,
|
||||||
rtnl::{Ifa, IfaFFlags, RtAddrFamily, Rtm},
|
rtnl::{Ifa, IfaF, RtAddrFamily, RtScope, Rtm},
|
||||||
socket::NlFamily,
|
socket::NlFamily,
|
||||||
},
|
},
|
||||||
nl::{NlPayload, Nlmsghdr},
|
nl::{NlPayload, NlmsghdrBuilder},
|
||||||
rtnl::{Ifaddrmsg, Rtattr},
|
rtnl::{IfaddrmsgBuilder, RtattrBuilder},
|
||||||
socket::NlSocketHandle,
|
socket::synchronous::NlSocketHandle,
|
||||||
types::RtBuffer,
|
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;
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
pub fn new_udp_reuseport(local_addr: SocketAddr) -> 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
|
// from tokio-rs/mio/blob/master/src/sys/unix/net.rs
|
||||||
udp_sock.set_cloexec(true).unwrap();
|
udp_sock.set_cloexec(true).unwrap();
|
||||||
udp_sock.set_nonblocking(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();
|
udp_sock.bind(&socket2::SockAddr::from(local_addr)).unwrap();
|
||||||
let udp_sock: std::net::UdpSocket = udp_sock.into();
|
let udp_sock: std::net::UdpSocket = udp_sock.into();
|
||||||
udp_sock.try_into().unwrap()
|
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) {
|
pub fn assign_ipv6_address(device_name: &str, local: Ipv6Addr, peer: Ipv6Addr) {
|
||||||
let index = nix::net::if_::if_nametoindex(device_name).unwrap();
|
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();
|
let mut rtattrs = RtBuffer::new();
|
||||||
rtattrs.push(Rtattr::new(None, Ifa::Local, &local.octets()[..]).unwrap());
|
rtattrs.push(
|
||||||
rtattrs.push(Rtattr::new(None, Ifa::Address, &peer.octets()[..]).unwrap());
|
RtattrBuilder::default()
|
||||||
|
.rta_type(Ifa::Local)
|
||||||
let ifaddrmsg = Ifaddrmsg {
|
.rta_payload(&local.octets()[..])
|
||||||
ifa_family: RtAddrFamily::Inet6,
|
.build()
|
||||||
ifa_prefixlen: 128,
|
.unwrap(),
|
||||||
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),
|
|
||||||
);
|
);
|
||||||
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
128
rpm/phantun.spec
Normal 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
26
selinux/Makefile
Normal 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
5
selinux/phantun.fc
Normal 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
60
selinux/phantun.te
Normal 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;
|
Reference in New Issue
Block a user