Merge branch 'blechschmidt:master' into docker_support

This commit is contained in:
Paper-Dragon 2023-09-01 12:20:12 +08:00 committed by GitHub
commit 0e86400d06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 241 additions and 104 deletions

View file

@ -35,7 +35,10 @@ jobs:
clippy: clippy:
name: Clippy name: Clippy
runs-on: ubuntu-latest strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
@ -48,3 +51,5 @@ jobs:
with: with:
command: clippy command: clippy
args: -- -D warnings args: -- -D warnings
- name: Build
run: cargo build --verbose

11
.github/workflows/install-cross.sh vendored Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
curl -s https://api.github.com/repos/cross-rs/cross/releases/latest \
| grep cross-x86_64-unknown-linux-gnu.tar.gz \
| cut -d : -f 2,3 \
| tr -d \" \
| wget -qi -
tar -zxvf cross-x86_64-unknown-linux-gnu.tar.gz -C /usr/bin
rm -f cross-x86_64-unknown-linux-gnu.tar.gz

View file

@ -3,28 +3,77 @@ on:
tags: tags:
- "*" - "*"
name: Build and publish executable name: Publish Releases
jobs: jobs:
build_publish: build_publish:
name: Build and publish executable name: Publishing Tasks
runs-on: ubuntu-latest strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- i686-unknown-linux-musl
- aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf
- x86_64-apple-darwin
- aarch64-apple-darwin
- x86_64-pc-windows-msvc
- i686-pc-windows-msvc
include:
- target: x86_64-unknown-linux-gnu
host_os: ubuntu-latest
- target: x86_64-unknown-linux-musl
host_os: ubuntu-latest
- target: i686-unknown-linux-musl
host_os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
host_os: ubuntu-latest
- target: armv7-unknown-linux-gnueabihf
host_os: ubuntu-latest
- target: x86_64-apple-darwin
host_os: macos-latest
- target: aarch64-apple-darwin
host_os: macos-latest
- target: x86_64-pc-windows-msvc
host_os: windows-latest
- target: i686-pc-windows-msvc
host_os: windows-latest
runs-on: ${{ matrix.host_os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with: - name: Prepare
profile: minimal shell: bash
toolchain: stable run: |
override: true mkdir publishdir
- uses: actions-rs/cargo@v1 rustup target add ${{ matrix.target }}
with: if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
command: build sudo .github/workflows/install-cross.sh
args: --release --target x86_64-unknown-linux-gnu fi
- name: Rename
run: mkdir build && mv target/x86_64-unknown-linux-gnu/release/tun2proxy build/tun2proxy-x86_64 - name: Build
shell: bash
run: |
if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
cross build --all-features --release --target ${{ matrix.target }}
else
cargo build --all-features --release --target ${{ matrix.target }}
fi
if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then
powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy.exe -DestinationPath publishdir/tun2proxy-${{ matrix.target }}.zip
elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then
zip -j publishdir/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy
elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
zip -j publishdir/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy
fi
- name: Publish - name: Publish
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
files: build/* files: publishdir/*

View file

@ -1,26 +1,31 @@
[package] [package]
authors = ["B. Blechschmidt"] authors = ["B. Blechschmidt"]
edition = "2018" edition = "2021"
name = "tun2proxy" name = "tun2proxy"
version = "0.1.5" version = "0.1.6"
[lib] [lib]
crate-type = ["cdylib", "lib"] crate-type = ["cdylib", "lib"]
[dependencies] [dependencies]
base64 = { version = "0.21" } base64 = { version = "0.21" }
clap = { version = "4.3", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
ctrlc = "3.4" ctrlc = "3.4"
digest_auth = "0.3" digest_auth = "0.3"
dotenvy = "0.15" dotenvy = "0.15"
env_logger = "0.10" env_logger = "0.10"
fork = "0.1"
hashlink = "0.8" hashlink = "0.8"
httparse = "1.8" httparse = "1.8"
libc = "0.2" libc = "0.2"
log = "0.4" log = "0.4"
mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] } mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] }
nix = { version = "0.26", features = ["process", "signal"] } nix = { version = "0.27", features = [
"process",
"signal",
"fs",
"mount",
"user",
] }
prctl = "1.0" prctl = "1.0"
smoltcp = { version = "0.10", features = ["std", "phy-tuntap_interface"] } smoltcp = { version = "0.10", features = ["std", "phy-tuntap_interface"] }
socks5-impl = { version = "0.5", default-features = false } socks5-impl = { version = "0.5", default-features = false }
@ -29,6 +34,9 @@ trust-dns-proto = "0.23"
unicase = "2.7" unicase = "2.7"
url = "2.4" url = "2.4"
[target.'cfg(target_family="unix")'.dependencies]
fork = "0.1"
[target.'cfg(target_os="android")'.dependencies] [target.'cfg(target_os="android")'.dependencies]
android_logger = "0.13" android_logger = "0.13"
jni = { version = "0.21", default-features = false } jni = { version = "0.21", default-features = false }

View file

@ -9,6 +9,7 @@ A tunnel interface for HTTP and SOCKS proxies on Linux based on [smoltcp](https:
- IPv4 and IPv6 support - IPv4 and IPv6 support
- GFW evasion mechanism for certain use cases (see [issue #35](https://github.com/blechschmidt/tun2proxy/issues/35)) - GFW evasion mechanism for certain use cases (see [issue #35](https://github.com/blechschmidt/tun2proxy/issues/35))
- SOCKS5 UDP support - SOCKS5 UDP support
- Native support for proxying DNS over TCP
## Build ## Build
Clone the repository and `cd` into the project folder. Then run the following: Clone the repository and `cd` into the project folder. Then run the following:
@ -91,10 +92,15 @@ Usage: tun2proxy [OPTIONS] --proxy <URL>
Options: Options:
-t, --tun <name> Name of the tun interface [default: tun0] -t, --tun <name> Name of the tun interface [default: tun0]
--tun-fd <fd> File descriptor of the tun interface
--tun-mtu <mtu> MTU of the tun interface (only with tunnel file descriptor) [default: 1500]
-p, --proxy <URL> Proxy URL in the form proto://[username[:password]@]host:port -p, --proxy <URL> Proxy URL in the form proto://[username[:password]@]host:port
-d, --dns <method> DNS handling [default: virtual] [possible values: virtual, none] -d, --dns <strategy> DNS handling strategy [default: virtual] [possible values: virtual, over-tcp, direct]
--dns-addr <IP> DNS resolver address [default: 8.8.8.8]
-6, --ipv6-enabled IPv6 enabled
-s, --setup <method> Routing and system setup [possible values: auto] -s, --setup <method> Routing and system setup [possible values: auto]
--bypass-ip <IP> Public proxy IP used in routing setup which should bypassing the tunnel --bypass-ip <IP> Public proxy IP used in routing setup which should bypassing the tunnel
-v, --verbosity <level> Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace]
-h, --help Print help -h, --help Print help
-V, --version Print version -V, --version Print version
``` ```
@ -146,6 +152,3 @@ asked to open connections to IPv6 destinations. In such a case, you can disable
either through `sysctl -w net.ipv6.conf.all.disable_ipv6=1` and `sysctl -w net.ipv6.conf.default.disable_ipv6=1` either through `sysctl -w net.ipv6.conf.all.disable_ipv6=1` and `sysctl -w net.ipv6.conf.default.disable_ipv6=1`
or through `ip -6 route del default`, which causes the `libc` resolver (and other software) to not issue DNS AAAA or through `ip -6 route del default`, which causes the `libc` resolver (and other software) to not issue DNS AAAA
requests for IPv6 addresses. requests for IPv6 addresses.
## TODO
- Native support for proxying DNS over TCP or TLS

View file

@ -52,6 +52,7 @@ pub enum Error {
#[error("{0}")] #[error("{0}")]
String(String), String(String),
#[cfg(target_family = "unix")]
#[error("nix::errno::Errno {0:?}")] #[error("nix::errno::Errno {0:?}")]
OSError(#[from] nix::errno::Errno), OSError(#[from] nix::errno::Errno),

View file

@ -2,7 +2,7 @@ use crate::{
error::Error, error::Error,
tun2proxy::{ tun2proxy::{
ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent,
OutgoingDirection, TcpProxy, OutgoingDirection, ProxyHandler,
}, },
}; };
use base64::Engine; use base64::Engine;
@ -317,7 +317,7 @@ impl HttpConnection {
} }
} }
impl TcpProxy for HttpConnection { impl ProxyHandler for HttpConnection {
fn get_connection_info(&self) -> &ConnectionInfo { fn get_connection_info(&self) -> &ConnectionInfo {
&self.info &self.info
} }
@ -395,7 +395,7 @@ pub(crate) struct HttpManager {
} }
impl ConnectionManager for HttpManager { impl ConnectionManager for HttpManager {
fn new_tcp_proxy(&self, info: &ConnectionInfo, _: bool) -> Result<Box<dyn TcpProxy>, Error> { fn new_proxy_handler(&self, info: &ConnectionInfo, _: bool) -> Result<Box<dyn ProxyHandler>, Error> {
if info.protocol != IpProtocol::Tcp { if info.protocol != IpProtocol::Tcp {
return Err("Invalid protocol".into()); return Err("Invalid protocol".into());
} }

View file

@ -29,6 +29,7 @@ pub struct Proxy {
pub enum NetworkInterface { pub enum NetworkInterface {
Named(String), Named(String),
#[cfg(target_family = "unix")]
Fd(std::os::fd::RawFd), Fd(std::os::fd::RawFd),
} }
@ -99,6 +100,7 @@ pub struct Options {
virtual_dns: Option<virtdns::VirtualDns>, virtual_dns: Option<virtdns::VirtualDns>,
mtu: Option<usize>, mtu: Option<usize>,
dns_over_tcp: bool, dns_over_tcp: bool,
dns_addr: Option<std::net::IpAddr>,
ipv6_enabled: bool, ipv6_enabled: bool,
} }
@ -119,6 +121,11 @@ impl Options {
self self
} }
pub fn with_dns_addr(mut self, addr: Option<std::net::IpAddr>) -> Self {
self.dns_addr = addr;
self
}
pub fn with_ipv6_enabled(mut self) -> Self { pub fn with_ipv6_enabled(mut self) -> Self {
self.ipv6_enabled = true; self.ipv6_enabled = true;
self self

View file

@ -25,13 +25,13 @@ struct Args {
#[arg(short, long, value_parser = Proxy::from_url, value_name = "URL")] #[arg(short, long, value_parser = Proxy::from_url, value_name = "URL")]
proxy: Proxy, proxy: Proxy,
/// DNS handling /// DNS handling strategy
#[arg(short, long, value_name = "method", value_enum, default_value = "virtual")] #[arg(short, long, value_name = "strategy", value_enum, default_value = "virtual")]
dns: ArgDns, dns: ArgDns,
/// Enable DNS over TCP /// DNS resolver address
#[arg(long)] #[arg(long, value_name = "IP", default_value = "8.8.8.8")]
dns_over_tcp: bool, dns_addr: IpAddr,
/// IPv6 enabled /// IPv6 enabled
#[arg(short = '6', long)] #[arg(short = '6', long)]
@ -50,10 +50,15 @@ struct Args {
verbosity: ArgVerbosity, verbosity: ArgVerbosity,
} }
/// DNS query handling strategy
/// - Virtual: Intercept DNS queries and resolve them locally with a fake IP address
/// - OverTcp: Use TCP to send DNS queries to the DNS server
/// - Direct: Looks as general UDP traffic but change the destination to the DNS server
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
enum ArgDns { enum ArgDns {
Virtual, Virtual,
None, OverTcp,
Direct,
} }
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
@ -83,23 +88,31 @@ fn main() -> ExitCode {
log::info!("Proxy {proxy_type} server: {addr}"); log::info!("Proxy {proxy_type} server: {addr}");
let mut options = Options::new(); let mut options = Options::new();
if args.dns == ArgDns::Virtual { match args.dns {
ArgDns::Virtual => {
options = options.with_virtual_dns(); options = options.with_virtual_dns();
} }
ArgDns::OverTcp => {
if args.dns_over_tcp {
options = options.with_dns_over_tcp(); options = options.with_dns_over_tcp();
} }
_ => {}
}
options = options.with_dns_addr(Some(args.dns_addr));
if args.ipv6_enabled { if args.ipv6_enabled {
options = options.with_ipv6_enabled(); options = options.with_ipv6_enabled();
} }
#[allow(unused_assignments)]
let interface = match args.tun_fd { let interface = match args.tun_fd {
None => NetworkInterface::Named(args.tun.clone()), None => NetworkInterface::Named(args.tun.clone()),
Some(fd) => { Some(_fd) => {
options = options.with_mtu(args.tun_mtu); options = options.with_mtu(args.tun_mtu);
NetworkInterface::Fd(fd) #[cfg(not(target_family = "unix"))]
panic!("Not supported");
#[cfg(target_family = "unix")]
NetworkInterface::Fd(_fd)
} }
}; };

View file

@ -2,7 +2,7 @@ use crate::{
error::{Error, Result}, error::{Error, Result},
tun2proxy::{ tun2proxy::{
ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent,
OutgoingDirection, TcpProxy, OutgoingDirection, ProxyHandler,
}, },
}; };
use socks5_impl::protocol::{self, handshake, password_method, Address, AuthMethod, StreamOperation, UserKey, Version}; use socks5_impl::protocol::{self, handshake, password_method, Address, AuthMethod, StreamOperation, UserKey, Version};
@ -268,7 +268,7 @@ impl SocksProxyImpl {
} }
} }
impl TcpProxy for SocksProxyImpl { impl ProxyHandler for SocksProxyImpl {
fn get_connection_info(&self) -> &ConnectionInfo { fn get_connection_info(&self) -> &ConnectionInfo {
&self.info &self.info
} }
@ -346,7 +346,7 @@ pub(crate) struct SocksProxyManager {
} }
impl ConnectionManager for SocksProxyManager { impl ConnectionManager for SocksProxyManager {
fn new_tcp_proxy(&self, info: &ConnectionInfo, udp_associate: bool) -> Result<Box<dyn TcpProxy>> { fn new_proxy_handler(&self, info: &ConnectionInfo, udp_associate: bool) -> Result<Box<dyn ProxyHandler>> {
use socks5_impl::protocol::Command::{Connect, UdpAssociate}; use socks5_impl::protocol::Command::{Connect, UdpAssociate};
let command = if udp_associate { UdpAssociate } else { Connect }; let command = if udp_associate { UdpAssociate } else { Connect };
let credentials = self.credentials.clone(); let credentials = self.credentials.clone();

View file

@ -1,20 +1,32 @@
#![allow(dead_code)]
use crate::{dns, error::Error, error::Result, virtdevice::VirtualTunDevice, NetworkInterface, Options}; use crate::{dns, error::Error, error::Result, virtdevice::VirtualTunDevice, NetworkInterface, Options};
use mio::{event::Event, net::TcpStream, net::UdpSocket, unix::SourceFd, Events, Interest, Poll, Token}; #[cfg(target_family = "unix")]
use mio::unix::SourceFd;
use mio::{event::Event, net::TcpStream, net::UdpSocket, Events, Interest, Poll, Token};
#[cfg(not(target_family = "unix"))]
use smoltcp::phy::DeviceCapabilities;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use smoltcp::phy::RawSocket;
#[cfg(any(target_os = "linux", target_os = "android"))]
use smoltcp::phy::TunTapInterface;
#[cfg(target_family = "unix")]
use smoltcp::phy::{Device, Medium, RxToken, TxToken};
use smoltcp::{ use smoltcp::{
iface::{Config, Interface, SocketHandle, SocketSet}, iface::{Config, Interface, SocketHandle, SocketSet},
phy::{Device, Medium, RxToken, TunTapInterface, TxToken},
socket::{tcp, tcp::State, udp, udp::UdpMetadata}, socket::{tcp, tcp::State, udp, udp::UdpMetadata},
time::Instant, time::Instant,
wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket, UDP_HEADER_LEN}, wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket, UDP_HEADER_LEN},
}; };
use socks5_impl::protocol::{Address, StreamOperation, UdpHeader, UserKey}; use socks5_impl::protocol::{Address, StreamOperation, UdpHeader, UserKey};
use std::collections::LinkedList; use std::collections::LinkedList;
#[cfg(target_family = "unix")]
use std::os::unix::io::AsRawFd;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
convert::{From, TryFrom}, convert::{From, TryFrom},
io::{Read, Write}, io::{Read, Write},
net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}, net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr},
os::unix::io::AsRawFd,
rc::Rc, rc::Rc,
str::FromStr, str::FromStr,
}; };
@ -174,7 +186,7 @@ struct ConnectionState {
smoltcp_handle: Option<SocketHandle>, smoltcp_handle: Option<SocketHandle>,
mio_stream: TcpStream, mio_stream: TcpStream,
token: Token, token: Token,
tcp_proxy_handler: Box<dyn TcpProxy>, proxy_handler: Box<dyn ProxyHandler>,
close_state: u8, close_state: u8,
wait_read: bool, wait_read: bool,
wait_write: bool, wait_write: bool,
@ -183,10 +195,10 @@ struct ConnectionState {
udp_token: Option<Token>, udp_token: Option<Token>,
origin_dst: SocketAddr, origin_dst: SocketAddr,
udp_data_cache: LinkedList<Vec<u8>>, udp_data_cache: LinkedList<Vec<u8>>,
udp_over_tcp_expiry: Option<::std::time::Instant>, dns_over_tcp_expiry: Option<::std::time::Instant>,
} }
pub(crate) trait TcpProxy { pub(crate) trait ProxyHandler {
fn get_connection_info(&self) -> &ConnectionInfo; fn get_connection_info(&self) -> &ConnectionInfo;
fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), Error>; fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), Error>;
fn consume_data(&mut self, dir: OutgoingDirection, size: usize); fn consume_data(&mut self, dir: OutgoingDirection, size: usize);
@ -198,7 +210,7 @@ pub(crate) trait TcpProxy {
} }
pub(crate) trait ConnectionManager { pub(crate) trait ConnectionManager {
fn new_tcp_proxy(&self, info: &ConnectionInfo, udp_associate: bool) -> Result<Box<dyn TcpProxy>>; fn new_proxy_handler(&self, info: &ConnectionInfo, udp_associate: bool) -> Result<Box<dyn ProxyHandler>>;
fn close_connection(&self, info: &ConnectionInfo); fn close_connection(&self, info: &ConnectionInfo);
fn get_server_addr(&self) -> SocketAddr; fn get_server_addr(&self) -> SocketAddr;
fn get_credentials(&self) -> &Option<UserKey>; fn get_credentials(&self) -> &Option<UserKey>;
@ -208,7 +220,10 @@ const TUN_TOKEN: Token = Token(0);
const EXIT_TOKEN: Token = Token(2); const EXIT_TOKEN: Token = Token(2);
pub struct TunToProxy<'a> { pub struct TunToProxy<'a> {
#[cfg(any(target_os = "linux", target_os = "android"))]
tun: TunTapInterface, tun: TunTapInterface,
#[cfg(any(target_os = "macos", target_os = "ios"))]
tun: RawSocket,
poll: Poll, poll: Poll,
iface: Interface, iface: Interface,
connection_map: HashMap<ConnectionInfo, ConnectionState>, connection_map: HashMap<ConnectionInfo, ConnectionState>,
@ -218,31 +233,53 @@ pub struct TunToProxy<'a> {
device: VirtualTunDevice, device: VirtualTunDevice,
options: Options, options: Options,
write_sockets: HashSet<Token>, write_sockets: HashSet<Token>,
#[cfg(target_family = "unix")]
_exit_receiver: mio::unix::pipe::Receiver, _exit_receiver: mio::unix::pipe::Receiver,
#[cfg(target_family = "unix")]
exit_sender: mio::unix::pipe::Sender, exit_sender: mio::unix::pipe::Sender,
} }
impl<'a> TunToProxy<'a> { impl<'a> TunToProxy<'a> {
pub fn new(interface: &NetworkInterface, options: Options) -> Result<Self, Error> { pub fn new(_interface: &NetworkInterface, options: Options) -> Result<Self, Error> {
let tun = match interface { #[cfg(any(target_os = "linux", target_os = "android"))]
let tun = match _interface {
NetworkInterface::Named(name) => TunTapInterface::new(name.as_str(), Medium::Ip)?, NetworkInterface::Named(name) => TunTapInterface::new(name.as_str(), Medium::Ip)?,
NetworkInterface::Fd(fd) => TunTapInterface::from_fd(*fd, Medium::Ip, options.mtu.unwrap_or(1500))?, NetworkInterface::Fd(fd) => TunTapInterface::from_fd(*fd, Medium::Ip, options.mtu.unwrap_or(1500))?,
}; };
#[cfg(any(target_os = "macos", target_os = "ios"))]
let tun = match _interface {
NetworkInterface::Named(name) => RawSocket::new(name.as_str(), Medium::Ip)?,
NetworkInterface::Fd(_fd) => panic!("Not supported"),
};
let poll = Poll::new()?; let poll = Poll::new()?;
#[cfg(target_family = "unix")]
poll.registry() poll.registry()
.register(&mut SourceFd(&tun.as_raw_fd()), TUN_TOKEN, Interest::READABLE)?; .register(&mut SourceFd(&tun.as_raw_fd()), TUN_TOKEN, Interest::READABLE)?;
#[cfg(target_family = "unix")]
let (exit_sender, mut exit_receiver) = mio::unix::pipe::new()?; let (exit_sender, mut exit_receiver) = mio::unix::pipe::new()?;
#[cfg(target_family = "unix")]
poll.registry() poll.registry()
.register(&mut exit_receiver, EXIT_TOKEN, Interest::READABLE)?; .register(&mut exit_receiver, EXIT_TOKEN, Interest::READABLE)?;
#[cfg(target_family = "unix")]
#[rustfmt::skip] #[rustfmt::skip]
let config = match tun.capabilities().medium { let config = match tun.capabilities().medium {
Medium::Ethernet => Config::new(smoltcp::wire::EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()), Medium::Ethernet => Config::new(smoltcp::wire::EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()),
Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
Medium::Ieee802154 => todo!(), Medium::Ieee802154 => todo!(),
}; };
#[cfg(not(target_family = "unix"))]
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
#[cfg(target_family = "unix")]
let mut device = VirtualTunDevice::new(tun.capabilities()); let mut device = VirtualTunDevice::new(tun.capabilities());
#[cfg(not(target_family = "unix"))]
let mut device = VirtualTunDevice::new(DeviceCapabilities::default());
let gateway4: Ipv4Addr = Ipv4Addr::from_str("0.0.0.1")?; let gateway4: Ipv4Addr = Ipv4Addr::from_str("0.0.0.1")?;
let gateway6: Ipv6Addr = Ipv6Addr::from_str("::1")?; let gateway6: Ipv6Addr = Ipv6Addr::from_str("::1")?;
let mut iface = Interface::new(config, &mut device, Instant::now()); let mut iface = Interface::new(config, &mut device, Instant::now());
@ -255,6 +292,7 @@ impl<'a> TunToProxy<'a> {
iface.set_any_ip(true); iface.set_any_ip(true);
let tun = Self { let tun = Self {
#[cfg(target_family = "unix")]
tun, tun,
poll, poll,
iface, iface,
@ -265,7 +303,9 @@ impl<'a> TunToProxy<'a> {
device, device,
options, options,
write_sockets: HashSet::default(), write_sockets: HashSet::default(),
#[cfg(target_family = "unix")]
_exit_receiver: exit_receiver, _exit_receiver: exit_receiver,
#[cfg(target_family = "unix")]
exit_sender, exit_sender,
}; };
Ok(tun) Ok(tun)
@ -286,14 +326,15 @@ impl<'a> TunToProxy<'a> {
self.iface.poll(Instant::now(), &mut self.device, &mut self.sockets); self.iface.poll(Instant::now(), &mut self.device, &mut self.sockets);
while let Some(vec) = self.device.exfiltrate_packet() { while let Some(vec) = self.device.exfiltrate_packet() {
let slice = vec.as_slice(); let _slice = vec.as_slice();
// TODO: Actual write. Replace. // TODO: Actual write. Replace.
#[cfg(target_family = "unix")]
self.tun self.tun
.transmit(Instant::now()) .transmit(Instant::now())
.ok_or("tx token not available")? .ok_or("tx token not available")?
.consume(slice.len(), |buf| { .consume(_slice.len(), |buf| {
buf[..].clone_from_slice(slice); buf[..].clone_from_slice(_slice);
}); });
} }
Ok(()) Ok(())
@ -358,10 +399,10 @@ impl<'a> TunToProxy<'a> {
let mut closed_ends = 0; let mut closed_ends = 0;
if (state.close_state & SERVER_WRITE_CLOSED) == SERVER_WRITE_CLOSED if (state.close_state & SERVER_WRITE_CLOSED) == SERVER_WRITE_CLOSED
&& !state && !state
.tcp_proxy_handler .proxy_handler
.have_data(Direction::Incoming(IncomingDirection::FromServer)) .have_data(Direction::Incoming(IncomingDirection::FromServer))
&& !state && !state
.tcp_proxy_handler .proxy_handler
.have_data(Direction::Outgoing(OutgoingDirection::ToClient)) .have_data(Direction::Outgoing(OutgoingDirection::ToClient))
{ {
if let Some(handle) = state.smoltcp_handle { if let Some(handle) = state.smoltcp_handle {
@ -374,10 +415,10 @@ impl<'a> TunToProxy<'a> {
if (state.close_state & CLIENT_WRITE_CLOSED) == CLIENT_WRITE_CLOSED if (state.close_state & CLIENT_WRITE_CLOSED) == CLIENT_WRITE_CLOSED
&& !state && !state
.tcp_proxy_handler .proxy_handler
.have_data(Direction::Incoming(IncomingDirection::FromClient)) .have_data(Direction::Incoming(IncomingDirection::FromClient))
&& !state && !state
.tcp_proxy_handler .proxy_handler
.have_data(Direction::Outgoing(OutgoingDirection::ToServer)) .have_data(Direction::Outgoing(OutgoingDirection::ToServer))
{ {
// Close remote server // Close remote server
@ -411,7 +452,7 @@ impl<'a> TunToProxy<'a> {
direction: IncomingDirection::FromClient, direction: IncomingDirection::FromClient,
buffer: data, buffer: data,
}; };
error = state.tcp_proxy_handler.push_data(event); error = state.proxy_handler.push_data(event);
(data.len(), ()) (data.len(), ())
})?; })?;
} }
@ -464,7 +505,7 @@ impl<'a> TunToProxy<'a> {
let mut info = info; let mut info = info;
let port = origin_dst.port(); let port = origin_dst.port();
if port == DNS_PORT && info.protocol == IpProtocol::Udp && dns::addr_is_private(&origin_dst) { if port == DNS_PORT && info.protocol == IpProtocol::Udp && dns::addr_is_private(&origin_dst) {
let dns_addr: SocketAddr = "8.8.8.8:53".parse()?; // TODO: Configurable let dns_addr: SocketAddr = (self.options.dns_addr.ok_or("dns_addr")?, DNS_PORT).into();
info.dst = Address::from(dns_addr); info.dst = Address::from(dns_addr);
} }
info info
@ -493,9 +534,9 @@ impl<'a> TunToProxy<'a> {
if !self.connection_map.contains_key(info) { if !self.connection_map.contains_key(info) {
log::info!("DNS over TCP {} ({})", info, origin_dst); log::info!("DNS over TCP {} ({})", info, origin_dst);
let tcp_proxy_handler = manager.new_tcp_proxy(info, false)?; let proxy_handler = manager.new_proxy_handler(info, false)?;
let server_addr = manager.get_server_addr(); let server_addr = manager.get_server_addr();
let state = self.create_new_tcp_connection_state(server_addr, origin_dst, tcp_proxy_handler, false)?; let state = self.create_new_tcp_connection_state(server_addr, origin_dst, proxy_handler, false)?;
self.connection_map.insert(info.clone(), state); self.connection_map.insert(info.clone(), state);
// TODO: Move this 3 lines to the function end? // TODO: Move this 3 lines to the function end?
@ -514,21 +555,21 @@ impl<'a> TunToProxy<'a> {
let err = "udp over tcp state not find"; let err = "udp over tcp state not find";
let state = self.connection_map.get_mut(info).ok_or(err)?; let state = self.connection_map.get_mut(info).ok_or(err)?;
state.udp_over_tcp_expiry = Some(Self::common_udp_life_timeout()); state.dns_over_tcp_expiry = Some(Self::common_udp_life_timeout());
let data_event = IncomingDataEvent { let data_event = IncomingDataEvent {
direction: IncomingDirection::FromClient, direction: IncomingDirection::FromClient,
buffer: &buf, buffer: &buf,
}; };
state.tcp_proxy_handler.push_data(data_event)?; state.proxy_handler.push_data(data_event)?;
Ok(()) Ok(())
} }
fn receive_dns_over_tcp_packet_and_write_to_client(&mut self, info: &ConnectionInfo) -> Result<()> { fn receive_dns_over_tcp_packet_and_write_to_client(&mut self, info: &ConnectionInfo) -> Result<()> {
let err = "udp connection state not found"; let err = "udp connection state not found";
let state = self.connection_map.get_mut(info).ok_or(err)?; let state = self.connection_map.get_mut(info).ok_or(err)?;
assert!(state.udp_over_tcp_expiry.is_some()); assert!(state.dns_over_tcp_expiry.is_some());
state.udp_over_tcp_expiry = Some(Self::common_udp_life_timeout()); state.dns_over_tcp_expiry = Some(Self::common_udp_life_timeout());
// Code similar to the code in parent function. TODO: Cleanup. // Code similar to the code in parent function. TODO: Cleanup.
let mut vecbuf = Vec::<u8>::new(); let mut vecbuf = Vec::<u8>::new();
@ -548,13 +589,13 @@ impl<'a> TunToProxy<'a> {
direction: IncomingDirection::FromServer, direction: IncomingDirection::FromServer,
buffer: &data[0..read], buffer: &data[0..read],
}; };
if let Err(error) = state.tcp_proxy_handler.push_data(data_event) { if let Err(error) = state.proxy_handler.push_data(data_event) {
log::error!("{}", error); log::error!("{}", error);
self.remove_connection(&info.clone())?; self.remove_connection(&info.clone())?;
return Ok(()); return Ok(());
} }
let dns_event = state.tcp_proxy_handler.peek_data(OutgoingDirection::ToClient); let dns_event = state.proxy_handler.peek_data(OutgoingDirection::ToClient);
let mut buf = dns_event.buffer.to_vec(); let mut buf = dns_event.buffer.to_vec();
let mut to_send: LinkedList<Vec<u8>> = LinkedList::new(); let mut to_send: LinkedList<Vec<u8>> = LinkedList::new();
@ -574,9 +615,7 @@ impl<'a> TunToProxy<'a> {
let ip = dns::extract_ipaddr_from_dns_message(&message); let ip = dns::extract_ipaddr_from_dns_message(&message);
log::trace!("DNS over TCP query result: {} -> {:?}", name, ip); log::trace!("DNS over TCP query result: {} -> {:?}", name, ip);
state state.proxy_handler.consume_data(OutgoingDirection::ToClient, len + 2);
.tcp_proxy_handler
.consume_data(OutgoingDirection::ToClient, len + 2);
if !self.options.ipv6_enabled { if !self.options.ipv6_enabled {
dns::remove_ipv6_entries(&mut message); dns::remove_ipv6_entries(&mut message);
@ -597,9 +636,9 @@ impl<'a> TunToProxy<'a> {
Ok(()) Ok(())
} }
fn udp_over_tcp_timeout_expired(&self, info: &ConnectionInfo) -> bool { fn dns_over_tcp_timeout_expired(&self, info: &ConnectionInfo) -> bool {
if let Some(state) = self.connection_map.get(info) { if let Some(state) = self.connection_map.get(info) {
if let Some(expiry) = state.udp_over_tcp_expiry { if let Some(expiry) = state.dns_over_tcp_expiry {
return expiry < ::std::time::Instant::now(); return expiry < ::std::time::Instant::now();
} }
} }
@ -609,8 +648,8 @@ impl<'a> TunToProxy<'a> {
fn clearup_expired_dns_over_tcp(&mut self) -> Result<()> { fn clearup_expired_dns_over_tcp(&mut self) -> Result<()> {
let keys = self.connection_map.keys().cloned().collect::<Vec<_>>(); let keys = self.connection_map.keys().cloned().collect::<Vec<_>>();
for key in keys { for key in keys {
if self.udp_over_tcp_timeout_expired(&key) { if self.dns_over_tcp_timeout_expired(&key) {
log::trace!("UDP over TCP timeout: {}", key); log::trace!("DNS over TCP timeout: {}", key);
self.remove_connection(&key)?; self.remove_connection(&key)?;
} }
} }
@ -626,9 +665,9 @@ impl<'a> TunToProxy<'a> {
) -> Result<()> { ) -> Result<()> {
if !self.connection_map.contains_key(info) { if !self.connection_map.contains_key(info) {
log::info!("UDP associate session {} ({})", info, origin_dst); log::info!("UDP associate session {} ({})", info, origin_dst);
let tcp_proxy_handler = manager.new_tcp_proxy(info, true)?; let proxy_handler = manager.new_proxy_handler(info, true)?;
let server_addr = manager.get_server_addr(); let server_addr = manager.get_server_addr();
let state = self.create_new_tcp_connection_state(server_addr, origin_dst, tcp_proxy_handler, true)?; let state = self.create_new_tcp_connection_state(server_addr, origin_dst, proxy_handler, true)?;
self.connection_map.insert(info.clone(), state); self.connection_map.insert(info.clone(), state);
self.expect_smoltcp_send()?; self.expect_smoltcp_send()?;
@ -648,7 +687,7 @@ impl<'a> TunToProxy<'a> {
UdpHeader::new(0, info.dst.clone()).write_to_stream(&mut s5_udp_data)?; UdpHeader::new(0, info.dst.clone()).write_to_stream(&mut s5_udp_data)?;
s5_udp_data.extend_from_slice(payload); s5_udp_data.extend_from_slice(payload);
if let Some(udp_associate) = state.tcp_proxy_handler.get_udp_associate() { if let Some(udp_associate) = state.proxy_handler.get_udp_associate() {
// UDP associate session has been established, we can send packets directly... // UDP associate session has been established, we can send packets directly...
if let Some(socket) = state.udp_socket.as_ref() { if let Some(socket) = state.udp_socket.as_ref() {
socket.send_to(&s5_udp_data, udp_associate)?; socket.send_to(&s5_udp_data, udp_associate)?;
@ -677,9 +716,9 @@ impl<'a> TunToProxy<'a> {
if info.protocol == IpProtocol::Tcp { if info.protocol == IpProtocol::Tcp {
if _first_packet { if _first_packet {
let tcp_proxy_handler = manager.new_tcp_proxy(&info, false)?; let proxy_handler = manager.new_proxy_handler(&info, false)?;
let server = manager.get_server_addr(); let server = manager.get_server_addr();
let state = self.create_new_tcp_connection_state(server, origin_dst, tcp_proxy_handler, false)?; let state = self.create_new_tcp_connection_state(server, origin_dst, proxy_handler, false)?;
self.connection_map.insert(info.clone(), state); self.connection_map.insert(info.clone(), state);
log::info!("Connect done {} ({})", info, origin_dst); log::info!("Connect done {} ({})", info, origin_dst);
@ -732,7 +771,7 @@ impl<'a> TunToProxy<'a> {
&mut self, &mut self,
server_addr: SocketAddr, server_addr: SocketAddr,
dst: SocketAddr, dst: SocketAddr,
tcp_proxy_handler: Box<dyn TcpProxy>, proxy_handler: Box<dyn ProxyHandler>,
udp_associate: bool, udp_associate: bool,
) -> Result<ConnectionState> { ) -> Result<ConnectionState> {
let mut socket = tcp::Socket::new( let mut socket = tcp::Socket::new(
@ -767,7 +806,7 @@ impl<'a> TunToProxy<'a> {
smoltcp_handle: Some(handle), smoltcp_handle: Some(handle),
mio_stream: client, mio_stream: client,
token, token,
tcp_proxy_handler, proxy_handler,
close_state: 0, close_state: 0,
wait_read: true, wait_read: true,
wait_write: false, wait_write: false,
@ -776,7 +815,7 @@ impl<'a> TunToProxy<'a> {
udp_token, udp_token,
origin_dst: dst, origin_dst: dst,
udp_data_cache: LinkedList::new(), udp_data_cache: LinkedList::new(),
udp_over_tcp_expiry: None, dns_over_tcp_expiry: None,
}; };
Ok(state) Ok(state)
} }
@ -819,7 +858,7 @@ impl<'a> TunToProxy<'a> {
fn write_to_server(&mut self, info: &ConnectionInfo) -> Result<(), Error> { fn write_to_server(&mut self, info: &ConnectionInfo) -> Result<(), Error> {
if let Some(state) = self.connection_map.get_mut(info) { if let Some(state) = self.connection_map.get_mut(info) {
let event = state.tcp_proxy_handler.peek_data(OutgoingDirection::ToServer); let event = state.proxy_handler.peek_data(OutgoingDirection::ToServer);
let buffer_size = event.buffer.len(); let buffer_size = event.buffer.len();
if buffer_size == 0 { if buffer_size == 0 {
state.wait_write = false; state.wait_write = false;
@ -830,9 +869,7 @@ impl<'a> TunToProxy<'a> {
let result = state.mio_stream.write(event.buffer); let result = state.mio_stream.write(event.buffer);
match result { match result {
Ok(written) => { Ok(written) => {
state state.proxy_handler.consume_data(OutgoingDirection::ToServer, written);
.tcp_proxy_handler
.consume_data(OutgoingDirection::ToServer, written);
state.wait_write = written < buffer_size; state.wait_write = written < buffer_size;
Self::update_mio_socket_interest(&mut self.poll, state)?; Self::update_mio_socket_interest(&mut self.poll, state)?;
} }
@ -856,7 +893,7 @@ impl<'a> TunToProxy<'a> {
Some(handle) => handle, Some(handle) => handle,
None => break, None => break,
}; };
let event = state.tcp_proxy_handler.peek_data(OutgoingDirection::ToClient); let event = state.proxy_handler.peek_data(OutgoingDirection::ToClient);
let buflen = event.buffer.len(); let buflen = event.buffer.len();
let consumed; let consumed;
{ {
@ -867,9 +904,7 @@ impl<'a> TunToProxy<'a> {
virtual_dns.touch_ip(&IpAddr::from(socket.local_endpoint().unwrap().addr)); virtual_dns.touch_ip(&IpAddr::from(socket.local_endpoint().unwrap().addr));
} }
consumed = socket.send_slice(event.buffer)?; consumed = socket.send_slice(event.buffer)?;
state state.proxy_handler.consume_data(OutgoingDirection::ToClient, consumed);
.tcp_proxy_handler
.consume_data(OutgoingDirection::ToClient, consumed);
self.expect_smoltcp_send()?; self.expect_smoltcp_send()?;
if consumed < buflen { if consumed < buflen {
self.write_sockets.insert(token); self.write_sockets.insert(token);
@ -892,6 +927,7 @@ impl<'a> TunToProxy<'a> {
fn tun_event(&mut self, event: &Event) -> Result<(), Error> { fn tun_event(&mut self, event: &Event) -> Result<(), Error> {
if event.is_readable() { if event.is_readable() {
#[cfg(target_family = "unix")]
while let Some((rx_token, _)) = self.tun.receive(Instant::now()) { while let Some((rx_token, _)) = self.tun.receive(Instant::now()) {
rx_token.consume(|frame| self.receive_tun(frame))?; rx_token.consume(|frame| self.receive_tun(frame))?;
} }
@ -952,7 +988,7 @@ impl<'a> TunToProxy<'a> {
// Try to send the first UDP packets to remote SOCKS5 server for UDP associate session // Try to send the first UDP packets to remote SOCKS5 server for UDP associate session
if let Some(state) = self.connection_map.get_mut(info) { if let Some(state) = self.connection_map.get_mut(info) {
if let Some(udp_socket) = state.udp_socket.as_ref() { if let Some(udp_socket) = state.udp_socket.as_ref() {
if let Some(addr) = state.tcp_proxy_handler.get_udp_associate() { if let Some(addr) = state.proxy_handler.get_udp_associate() {
// Consume udp_data_cache data // Consume udp_data_cache data
while let Some(buf) = state.udp_data_cache.pop_front() { while let Some(buf) = state.udp_data_cache.pop_front() {
udp_socket.send_to(&buf, addr)?; udp_socket.send_to(&buf, addr)?;
@ -988,7 +1024,7 @@ impl<'a> TunToProxy<'a> {
.connection_map .connection_map
.get(&conn_info) .get(&conn_info)
.ok_or("")? .ok_or("")?
.tcp_proxy_handler .proxy_handler
.connection_established(); .connection_established();
if self.options.dns_over_tcp && conn_info.dst.port() == DNS_PORT && established { if self.options.dns_over_tcp && conn_info.dst.port() == DNS_PORT && established {
self.receive_dns_over_tcp_packet_and_write_to_client(&conn_info)?; self.receive_dns_over_tcp_packet_and_write_to_client(&conn_info)?;
@ -1015,14 +1051,14 @@ impl<'a> TunToProxy<'a> {
direction: IncomingDirection::FromServer, direction: IncomingDirection::FromServer,
buffer: &data[0..read], buffer: &data[0..read],
}; };
if let Err(error) = state.tcp_proxy_handler.push_data(data_event) { if let Err(error) = state.proxy_handler.push_data(data_event) {
log::error!("{}", error); log::error!("{}", error);
self.remove_connection(&conn_info.clone())?; self.remove_connection(&conn_info.clone())?;
return Ok(()); return Ok(());
} }
// The handler request for reset the server connection // The handler request for reset the server connection
if state.tcp_proxy_handler.reset_connection() { if state.proxy_handler.reset_connection() {
_ = self.poll.registry().deregister(&mut state.mio_stream); _ = self.poll.registry().deregister(&mut state.mio_stream);
// Closes the connection with the proxy // Closes the connection with the proxy
state.mio_stream.shutdown(Shutdown::Both)?; state.mio_stream.shutdown(Shutdown::Both)?;
@ -1098,6 +1134,7 @@ impl<'a> TunToProxy<'a> {
} }
pub fn shutdown(&mut self) -> Result<(), Error> { pub fn shutdown(&mut self) -> Result<(), Error> {
#[cfg(target_family = "unix")]
self.exit_sender.write_all(&[1])?; self.exit_sender.write_all(&[1])?;
Ok(()) Ok(())
} }

View file

@ -1,3 +1,5 @@
#![allow(dead_code)]
use crate::error::Result; use crate::error::Result;
use hashlink::{linked_hash_map::RawEntryMut, LruCache}; use hashlink::{linked_hash_map::RawEntryMut, LruCache};
use smoltcp::wire::Ipv4Cidr; use smoltcp::wire::Ipv4Cidr;

View file

@ -1,3 +1,4 @@
#[cfg(target_os = "linux")]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
extern crate reqwest; extern crate reqwest;