diff --git a/Cargo.toml b/Cargo.toml
index cedd349..11adae6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,7 @@ mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] }
nix = { version = "0.26", features = ["process", "signal"] }
prctl = "1.0"
smoltcp = { version = "0.10.0", features = ["std", "phy-tuntap_interface"] }
+socks5-impl = { version = "0.4", default-features = false }
thiserror = "1.0"
unicase = "2.6.0"
url = "2.4"
diff --git a/README.md b/README.md
index bc3ab83..1925a4e 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ Apart from SOCKS5, SOCKS4 and HTTP are supported.
Note that if your proxy is a non-global IP address (e.g. because the proxy is provided by some tunneling tool running
locally), you will additionally need to provide the public IP address of the server through which the traffic is
-actually tunneled. In such a case, the tool will tell you to specify the address through `--setup-ip
` if you
+actually tunneled. In such a case, the tool will tell you to specify the address through `--bypass-ip ` if you
wish to make use of the automated setup feature.
## Manual Setup
@@ -40,6 +40,7 @@ A standard setup, which would route all traffic from your system through the tun
PROXY_TYPE=SOCKS5
PROXY_IP=1.2.3.4
PROXY_PORT=1080
+BYPASS_IP=123.45.67.89
# Create a tunnel interface named tun0 which your user can bind to,
# so we don't need to run tun2proxy as root.
@@ -48,7 +49,7 @@ sudo ip link set tun0 up
# To prevent a routing loop, we add a route to the proxy server that behaves
# like the default route.
-sudo ip route add "$PROXY_IP" $(ip route | grep '^default' | cut -d ' ' -f 2-)
+sudo ip route add "$BYPASS_IP" $(ip route | grep '^default' | cut -d ' ' -f 2-)
# Route all your traffic through tun0 without interfering with the default route.
sudo ip route add 128.0.0.0/1 dev tun0
@@ -92,7 +93,7 @@ Options:
-p, --proxy Proxy URL in the form proto://[username[:password]@]host:port
-d, --dns DNS handling [default: virtual] [possible values: virtual, none]
-s, --setup Routing and system setup [possible values: auto]
- --setup-ip Public proxy IP used in routing setup
+ --bypass-ip Public proxy IP used in routing setup which should bypassing the tunnel
-h, --help Print help
-V, --version Print version
```
diff --git a/src/android.rs b/src/android.rs
index 4f642c8..02d4e5b 100644
--- a/src/android.rs
+++ b/src/android.rs
@@ -1,7 +1,6 @@
#![cfg(target_os = "android")]
-use crate::tun2proxy::TunToProxy;
-use crate::{error::Error, tun_to_proxy, NetworkInterface, Options, Proxy};
+use crate::{error::Error, tun2proxy::TunToProxy, tun_to_proxy, NetworkInterface, Options, Proxy};
use jni::{
objects::{JClass, JString},
sys::{jboolean, jint},
diff --git a/src/http.rs b/src/http.rs
index 3db3787..d5ff54f 100644
--- a/src/http.rs
+++ b/src/http.rs
@@ -1,19 +1,22 @@
-use crate::error::Error;
-use crate::tun2proxy::{
- Connection, ConnectionManager, Destination, Direction, IncomingDataEvent, IncomingDirection,
- OutgoingDataEvent, OutgoingDirection, TcpProxy,
+use crate::{
+ error::Error,
+ tun2proxy::{
+ Connection, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection,
+ OutgoingDataEvent, OutgoingDirection, TcpProxy,
+ },
};
-use crate::Credentials;
use base64::Engine;
use httparse::Response;
use smoltcp::wire::IpProtocol;
-use std::cell::RefCell;
-use std::collections::hash_map::RandomState;
-use std::collections::{HashMap, VecDeque};
-use std::iter::FromIterator;
-use std::net::SocketAddr;
-use std::rc::Rc;
-use std::str;
+use socks5_impl::protocol::{Address, UserKey};
+use std::{
+ cell::RefCell,
+ collections::{hash_map::RandomState, HashMap, VecDeque},
+ iter::FromIterator,
+ net::SocketAddr,
+ rc::Rc,
+ str,
+};
use unicase::UniCase;
#[derive(Eq, PartialEq, Debug)]
@@ -48,8 +51,8 @@ pub struct HttpConnection {
skip: usize,
digest_state: Rc>>,
before: bool,
- credentials: Option,
- destination: Destination,
+ credentials: Option,
+ destination: Address,
}
static PROXY_AUTHENTICATE: &str = "Proxy-Authenticate";
@@ -66,11 +69,11 @@ impl HttpConnection {
) -> Result {
let mut res = Self {
state: HttpState::ExpectResponseHeaders,
- client_inbuf: Default::default(),
- server_inbuf: Default::default(),
- client_outbuf: Default::default(),
- server_outbuf: Default::default(),
- data_buf: Default::default(),
+ client_inbuf: VecDeque::default(),
+ server_inbuf: VecDeque::default(),
+ client_outbuf: VecDeque::default(),
+ server_outbuf: VecDeque::default(),
+ data_buf: VecDeque::default(),
skip: 0,
counter: 0,
crlf_state: 0,
@@ -110,7 +113,7 @@ impl HttpConnection {
match scheme {
AuthenticationScheme::Digest => {
- let uri = format!("{}:{}", self.destination.host, self.destination.port);
+ let uri = self.destination.to_string();
let context = digest_auth::AuthContext::new_with_method(
&credentials.username,
@@ -386,7 +389,7 @@ impl TcpProxy for HttpConnection {
pub(crate) struct HttpManager {
server: SocketAddr,
- credentials: Option,
+ credentials: Option,
digest_state: Rc>>,
}
@@ -416,13 +419,13 @@ impl ConnectionManager for HttpManager {
self.server
}
- fn get_credentials(&self) -> &Option {
+ fn get_credentials(&self) -> &Option {
&self.credentials
}
}
impl HttpManager {
- pub fn new(server: SocketAddr, credentials: Option) -> Rc {
+ pub fn new(server: SocketAddr, credentials: Option) -> Rc {
Rc::new(Self {
server,
credentials,
diff --git a/src/lib.rs b/src/lib.rs
index 6d65302..2c743c0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,8 @@
-use crate::error::Error;
-use crate::socks::SocksVersion;
-use crate::{http::HttpManager, socks::SocksManager, tun2proxy::TunToProxy};
+use crate::{
+ error::Error, http::HttpManager, socks::SocksManager, socks::SocksVersion,
+ tun2proxy::TunToProxy,
+};
+use socks5_impl::protocol::UserKey;
use std::net::{SocketAddr, ToSocketAddrs};
mod android;
@@ -16,7 +18,7 @@ mod virtdns;
pub struct Proxy {
pub proxy_type: ProxyType,
pub addr: SocketAddr,
- pub credentials: Option,
+ pub credentials: Option,
}
pub enum NetworkInterface {
@@ -48,7 +50,7 @@ impl Proxy {
} else {
let username = String::from(url.username());
let password = String::from(url.password().unwrap_or(""));
- Some(Credentials::new(&username, &password))
+ Some(UserKey::new(username, password))
};
let scheme = url.scheme();
@@ -94,7 +96,7 @@ pub struct Options {
impl Options {
pub fn new() -> Self {
- Default::default()
+ Options::default()
}
pub fn with_virtual_dns(mut self) -> Self {
@@ -108,21 +110,6 @@ impl Options {
}
}
-#[derive(Default, Clone, Debug)]
-pub struct Credentials {
- pub(crate) username: String,
- pub(crate) password: String,
-}
-
-impl Credentials {
- pub fn new(username: &str, password: &str) -> Self {
- Self {
- username: String::from(username),
- password: String::from(password),
- }
- }
-}
-
pub fn tun_to_proxy<'a>(
interface: &NetworkInterface,
proxy: &Proxy,
diff --git a/src/main.rs b/src/main.rs
index 43b128e..bcf8519 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,7 @@
use clap::Parser;
use env_logger::Env;
-
-use std::net::IpAddr;
-use std::process::ExitCode;
-
-use tun2proxy::error::Error;
-use tun2proxy::{main_entry, Proxy};
-use tun2proxy::{NetworkInterface, Options};
+use std::{net::IpAddr, process::ExitCode};
+use tun2proxy::{error::Error, main_entry, NetworkInterface, Options, Proxy};
#[cfg(target_os = "linux")]
use tun2proxy::setup::{get_default_cidrs, Setup};
@@ -45,9 +40,9 @@ struct Args {
#[arg(short, long, value_name = "method", value_enum)]
setup: Option,
- /// Public proxy IP used in routing setup
+ /// Public proxy IP used in routing setup which should bypassing the tunnel
#[arg(long, value_name = "IP")]
- setup_ip: Option,
+ bypass_ip: Option,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
@@ -83,12 +78,12 @@ fn main() -> ExitCode {
}
};
- if let Err(e) = (|| -> Result<(), Error> {
+ let block = || -> Result<(), Error> {
#[cfg(target_os = "linux")]
{
let mut setup: Setup;
if args.setup == Some(ArgSetup::Auto) {
- let bypass_tun_ip = match args.setup_ip {
+ let bypass_tun_ip = match args.bypass_ip {
Some(addr) => addr,
None => args.proxy.addr.ip(),
};
@@ -96,7 +91,7 @@ fn main() -> ExitCode {
&args.tun,
&bypass_tun_ip,
get_default_cidrs(),
- args.setup_ip.is_some(),
+ args.bypass_ip.is_some(),
);
setup.configure()?;
@@ -108,10 +103,11 @@ fn main() -> ExitCode {
main_entry(&interface, &args.proxy, options)?;
Ok(())
- })() {
+ };
+ if let Err(e) = block() {
log::error!("{e}");
return ExitCode::FAILURE;
- };
+ }
ExitCode::SUCCESS
}
diff --git a/src/setup.rs b/src/setup.rs
index 7728d0f..8838ab3 100644
--- a/src/setup.rs
+++ b/src/setup.rs
@@ -1,20 +1,17 @@
#![cfg(target_os = "linux")]
use crate::error::Error;
-use smoltcp::wire::IpCidr;
-use std::convert::TryFrom;
-
-use std::ffi::OsStr;
-use std::io::BufRead;
-use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
-
-use std::os::unix::io::RawFd;
-
-use std::process::{Command, Output};
-
-use std::str::FromStr;
-
use fork::Fork;
+use smoltcp::wire::IpCidr;
+use std::{
+ convert::TryFrom,
+ ffi::OsStr,
+ io::BufRead,
+ net::{IpAddr, Ipv4Addr, Ipv6Addr},
+ os::unix::io::RawFd,
+ process::{Command, Output},
+ str::FromStr,
+};
#[derive(Clone)]
pub struct Setup {
@@ -320,7 +317,7 @@ impl Setup {
if self.tunnel_bypass_addr.is_loopback() && !self.allow_private {
log::warn!(
"The proxy address {} is a loopback address. You may need to manually \
- provide --setup-ip to specify the server IP bypassing the tunnel",
+ provide --bypass-ip to specify the server IP bypassing the tunnel",
self.tunnel_bypass_addr
)
}
diff --git a/src/socks.rs b/src/socks.rs
index ed1b477..cb460f8 100644
--- a/src/socks.rs
+++ b/src/socks.rs
@@ -1,16 +1,13 @@
-use std::collections::VecDeque;
-use std::convert::TryFrom;
-use std::net::{IpAddr, SocketAddr};
-use std::rc::Rc;
-
-use smoltcp::wire::IpProtocol;
-
-use crate::error::Error;
-use crate::tun2proxy::{
- Connection, ConnectionManager, DestinationHost, Direction, IncomingDataEvent,
- IncomingDirection, OutgoingDataEvent, OutgoingDirection, TcpProxy,
+use crate::{
+ error::Error,
+ tun2proxy::{
+ Connection, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection,
+ OutgoingDataEvent, OutgoingDirection, TcpProxy,
+ },
};
-use crate::Credentials;
+use smoltcp::wire::IpProtocol;
+use socks5_impl::protocol::{self, Address, AddressType, UserKey};
+use std::{collections::VecDeque, convert::TryFrom, net::SocketAddr, rc::Rc};
#[derive(Eq, PartialEq, Debug)]
#[allow(dead_code)]
@@ -24,32 +21,6 @@ enum SocksState {
Established,
}
-#[repr(u8)]
-#[derive(Copy, Clone, PartialEq, Debug)]
-enum SocksAddressType {
- Ipv4 = 1,
- DomainName = 3,
- Ipv6 = 4,
-}
-
-impl TryFrom for SocksAddressType {
- type Error = Error;
- fn try_from(value: u8) -> Result {
- match value {
- 1 => Ok(SocksAddressType::Ipv4),
- 3 => Ok(SocksAddressType::DomainName),
- 4 => Ok(SocksAddressType::Ipv6),
- _ => Err(format!("Unknown address type: {}", value).into()),
- }
- }
-}
-
-impl From for u8 {
- fn from(value: SocksAddressType) -> Self {
- value as u8
- }
-}
-
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum SocksVersion {
@@ -57,15 +28,6 @@ pub enum SocksVersion {
V5 = 5,
}
-#[repr(u8)]
-#[derive(Copy, Clone, PartialEq, Debug)]
-#[allow(dead_code)]
-pub enum SocksCommand {
- Connect = 1,
- Bind = 2,
- UdpAssociate = 3,
-}
-
#[allow(dead_code)]
enum SocksAuthentication {
None = 0,
@@ -105,7 +67,7 @@ pub(crate) struct SocksConnection {
server_outbuf: VecDeque,
data_buf: VecDeque,
version: SocksVersion,
- credentials: Option,
+ credentials: Option,
}
impl SocksConnection {
@@ -133,22 +95,20 @@ impl SocksConnection {
let credentials = &self.credentials;
match self.version {
SocksVersion::V4 => {
- self.server_outbuf.extend(&[
- self.version as u8,
- SocksCommand::Connect as u8,
- (self.connection.dst.port >> 8) as u8,
- (self.connection.dst.port & 0xff) as u8,
- ]);
+ self.server_outbuf
+ .extend(&[self.version as u8, protocol::Command::Connect.into()]);
+ self.server_outbuf
+ .extend(self.connection.dst.port().to_be_bytes());
let mut ip_vec = Vec::::new();
let mut name_vec = Vec::::new();
- match &self.connection.dst.host {
- DestinationHost::Address(dst_ip) => {
- match dst_ip {
- IpAddr::V4(ip) => ip_vec.extend(ip.octets().as_ref()),
- IpAddr::V6(_) => return Err("SOCKS4 does not support IPv6".into()),
- };
+ match &self.connection.dst {
+ Address::SocketAddress(SocketAddr::V4(addr)) => {
+ ip_vec.extend(addr.ip().octets().as_ref());
}
- DestinationHost::Hostname(host) => {
+ Address::SocketAddress(SocketAddr::V6(_)) => {
+ return Err("SOCKS4 does not support IPv6".into());
+ }
+ Address::DomainAddress(host, _) => {
ip_vec.extend(&[0, 0, 0, host.len() as u8]);
name_vec.extend(host.as_bytes());
name_vec.push(0);
@@ -246,7 +206,7 @@ impl SocksConnection {
}
fn send_auth_data(&mut self) -> Result<(), Error> {
- let tmp = Credentials::default();
+ let tmp = UserKey::default();
let credentials = self.credentials.as_ref().unwrap_or(&tmp);
self.server_outbuf
.extend(&[1u8, credentials.username.len() as u8]);
@@ -287,8 +247,8 @@ impl SocksConnection {
return Err("SOCKS5 connection unsuccessful.".into());
}
- let message_length = match SocksAddressType::try_from(atyp)? {
- SocksAddressType::DomainName => {
+ let message_length = match AddressType::try_from(atyp)? {
+ AddressType::Domain => {
if self.server_inbuf.len() < 5 {
return Ok(());
}
@@ -297,8 +257,8 @@ impl SocksConnection {
}
7 + (self.server_inbuf[4] as usize)
}
- SocksAddressType::Ipv4 => 10,
- SocksAddressType::Ipv6 => 22,
+ AddressType::IPv4 => 10,
+ AddressType::IPv6 => 22,
};
self.server_inbuf.drain(0..message_length);
@@ -310,30 +270,8 @@ impl SocksConnection {
}
fn send_request(&mut self) -> Result<(), Error> {
- self.server_outbuf.extend(&[5u8, 1, 0]);
- match &self.connection.dst.host {
- DestinationHost::Address(dst_ip) => {
- let cmd = if dst_ip.is_ipv4() {
- SocksAddressType::Ipv4
- } else {
- SocksAddressType::Ipv6
- };
- self.server_outbuf.extend(&[u8::from(cmd)]);
- match dst_ip {
- IpAddr::V4(ip) => self.server_outbuf.extend(ip.octets().as_ref()),
- IpAddr::V6(ip) => self.server_outbuf.extend(ip.octets().as_ref()),
- };
- }
- DestinationHost::Hostname(host) => {
- self.server_outbuf
- .extend(&[u8::from(SocksAddressType::DomainName), host.len() as u8]);
- self.server_outbuf.extend(host.as_bytes());
- }
- }
- self.server_outbuf.extend(&[
- (self.connection.dst.port >> 8) as u8,
- (self.connection.dst.port & 0xff) as u8,
- ]);
+ protocol::Request::new(protocol::Command::Connect, self.connection.dst.clone())
+ .write_to_stream(&mut self.server_outbuf)?;
self.state = SocksState::ReceiveResponse;
self.state_change()
}
@@ -432,7 +370,7 @@ impl TcpProxy for SocksConnection {
pub struct SocksManager {
server: SocketAddr,
- credentials: Option,
+ credentials: Option,
version: SocksVersion,
}
@@ -462,7 +400,7 @@ impl ConnectionManager for SocksManager {
self.server
}
- fn get_credentials(&self) -> &Option {
+ fn get_credentials(&self) -> &Option {
&self.credentials
}
}
@@ -471,7 +409,7 @@ impl SocksManager {
pub fn new(
server: SocketAddr,
version: SocksVersion,
- credentials: Option,
+ credentials: Option,
) -> Rc {
Rc::new(Self {
server,
diff --git a/src/tun2proxy.rs b/src/tun2proxy.rs
index 6bc9529..d5af74a 100644
--- a/src/tun2proxy.rs
+++ b/src/tun2proxy.rs
@@ -1,91 +1,35 @@
-use crate::error::Error;
-use crate::virtdevice::VirtualTunDevice;
-use crate::{Credentials, NetworkInterface, Options};
-use log::{error, info};
-use mio::event::Event;
-use mio::net::TcpStream;
-use mio::unix::SourceFd;
-use mio::{Events, Interest, Poll, Token};
-use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet};
-use smoltcp::phy::{Device, Medium, RxToken, TunTapInterface, TxToken};
-use smoltcp::socket::tcp::State;
-use smoltcp::socket::udp::UdpMetadata;
-use smoltcp::socket::{tcp, udp};
-use smoltcp::time::Instant;
-use smoltcp::wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket};
-use std::collections::{HashMap, HashSet};
-use std::convert::{From, TryFrom};
-use std::io::{Read, Write};
-use std::net::Shutdown::Both;
-use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
-use std::os::unix::io::AsRawFd;
-use std::rc::Rc;
-use std::str::FromStr;
-
-#[derive(Hash, Clone, Eq, PartialEq, Debug)]
-pub(crate) enum DestinationHost {
- Address(IpAddr),
- Hostname(String),
-}
-
-impl std::fmt::Display for DestinationHost {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- DestinationHost::Address(addr) => addr.fmt(f),
- DestinationHost::Hostname(name) => name.fmt(f),
- }
- }
-}
-
-#[derive(Hash, Clone, Eq, PartialEq, Debug)]
-pub(crate) struct Destination {
- pub(crate) host: DestinationHost,
- pub(crate) port: u16,
-}
-
-impl TryFrom for SocketAddr {
- type Error = Error;
- fn try_from(value: Destination) -> Result {
- let ip = match value.host {
- DestinationHost::Address(addr) => addr,
- DestinationHost::Hostname(e) => {
- return Err(e.into());
- }
- };
- Ok(SocketAddr::new(ip, value.port))
- }
-}
-
-impl From for Destination {
- fn from(addr: SocketAddr) -> Self {
- Self {
- host: DestinationHost::Address(addr.ip()),
- port: addr.port(),
- }
- }
-}
-
-impl std::fmt::Display for Destination {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if let DestinationHost::Address(IpAddr::V6(addr)) = self.host {
- write!(f, "[{}]:{}", addr, self.port)
- } else {
- write!(f, "{}:{}", self.host, self.port)
- }
- }
-}
+use crate::{error::Error, virtdevice::VirtualTunDevice, NetworkInterface, Options};
+use mio::{event::Event, net::TcpStream, unix::SourceFd, Events, Interest, Poll, Token};
+use smoltcp::{
+ iface::{Config, Interface, SocketHandle, SocketSet},
+ phy::{Device, Medium, RxToken, TunTapInterface, TxToken},
+ socket::{tcp, tcp::State, udp, udp::UdpMetadata},
+ time::Instant,
+ wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket},
+};
+use socks5_impl::protocol::{Address, UserKey};
+use std::{
+ collections::{HashMap, HashSet},
+ convert::{From, TryFrom},
+ io::{Read, Write},
+ net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, Shutdown::Both, SocketAddr},
+ os::unix::io::AsRawFd,
+ rc::Rc,
+ str::FromStr,
+};
#[derive(Hash, Clone, Eq, PartialEq, Debug)]
pub(crate) struct Connection {
pub(crate) src: SocketAddr,
- pub(crate) dst: Destination,
+ pub(crate) dst: Address,
pub(crate) proto: IpProtocol,
}
impl Connection {
fn to_named(&self, name: String) -> Self {
let mut result = self.clone();
- result.dst.host = DestinationHost::Hostname(name);
+ result.dst = Address::from((name, result.dst.port()));
+ log::trace!("Replace dst \"{}\" -> \"{}\"", self.dst, result.dst);
result
}
}
@@ -160,7 +104,7 @@ fn connection_tuple(frame: &[u8]) -> Option<(Connection, bool, usize, usize)> {
if let Ok(packet) = Ipv4Packet::new_checked(frame) {
let proto = packet.next_header();
- let mut a: [u8; 4] = Default::default();
+ let mut a = [0_u8; 4];
a.copy_from_slice(packet.src_addr().as_bytes());
let src_addr = IpAddr::from(a);
a.copy_from_slice(packet.dst_addr().as_bytes());
@@ -187,7 +131,7 @@ fn connection_tuple(frame: &[u8]) -> Option<(Connection, bool, usize, usize)> {
// TODO: Support extension headers.
let proto = packet.next_header();
- let mut a: [u8; 16] = Default::default();
+ let mut a = [0_u8; 16];
a.copy_from_slice(packet.src_addr().as_bytes());
let src_addr = IpAddr::from(a);
a.copy_from_slice(packet.dst_addr().as_bytes());
@@ -241,7 +185,7 @@ pub(crate) trait ConnectionManager {
) -> Result