swith socks5-impl

This commit is contained in:
ssrlive 2023-07-23 02:03:15 +08:00 committed by B. Blechschmidt
parent ab9f8011f0
commit c61b6c74cd
11 changed files with 163 additions and 301 deletions

View file

@ -23,6 +23,7 @@ mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] }
nix = { version = "0.26", features = ["process", "signal"] } nix = { version = "0.26", features = ["process", "signal"] }
prctl = "1.0" prctl = "1.0"
smoltcp = { version = "0.10.0", features = ["std", "phy-tuntap_interface"] } smoltcp = { version = "0.10.0", features = ["std", "phy-tuntap_interface"] }
socks5-impl = { version = "0.4", default-features = false }
thiserror = "1.0" thiserror = "1.0"
unicase = "2.6.0" unicase = "2.6.0"
url = "2.4" url = "2.4"

View file

@ -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 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 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 <address>` if you actually tunneled. In such a case, the tool will tell you to specify the address through `--bypass-ip <address>` if you
wish to make use of the automated setup feature. wish to make use of the automated setup feature.
## Manual Setup ## Manual Setup
@ -40,6 +40,7 @@ A standard setup, which would route all traffic from your system through the tun
PROXY_TYPE=SOCKS5 PROXY_TYPE=SOCKS5
PROXY_IP=1.2.3.4 PROXY_IP=1.2.3.4
PROXY_PORT=1080 PROXY_PORT=1080
BYPASS_IP=123.45.67.89
# Create a tunnel interface named tun0 which your user can bind to, # Create a tunnel interface named tun0 which your user can bind to,
# so we don't need to run tun2proxy as root. # 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 # To prevent a routing loop, we add a route to the proxy server that behaves
# like the default route. # 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. # Route all your traffic through tun0 without interfering with the default route.
sudo ip route add 128.0.0.0/1 dev tun0 sudo ip route add 128.0.0.0/1 dev tun0
@ -92,7 +93,7 @@ Options:
-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 <method> DNS handling [default: virtual] [possible values: virtual, none]
-s, --setup <method> Routing and system setup [possible values: auto] -s, --setup <method> Routing and system setup [possible values: auto]
--setup-ip <IP> Public proxy IP used in routing setup --bypass-ip <IP> Public proxy IP used in routing setup which should bypassing the tunnel
-h, --help Print help -h, --help Print help
-V, --version Print version -V, --version Print version
``` ```

View file

@ -1,7 +1,6 @@
#![cfg(target_os = "android")] #![cfg(target_os = "android")]
use crate::tun2proxy::TunToProxy; use crate::{error::Error, tun2proxy::TunToProxy, tun_to_proxy, NetworkInterface, Options, Proxy};
use crate::{error::Error, tun_to_proxy, NetworkInterface, Options, Proxy};
use jni::{ use jni::{
objects::{JClass, JString}, objects::{JClass, JString},
sys::{jboolean, jint}, sys::{jboolean, jint},

View file

@ -1,19 +1,22 @@
use crate::error::Error; use crate::{
use crate::tun2proxy::{ error::Error,
Connection, ConnectionManager, Destination, Direction, IncomingDataEvent, IncomingDirection, tun2proxy::{
OutgoingDataEvent, OutgoingDirection, TcpProxy, Connection, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection,
OutgoingDataEvent, OutgoingDirection, TcpProxy,
},
}; };
use crate::Credentials;
use base64::Engine; use base64::Engine;
use httparse::Response; use httparse::Response;
use smoltcp::wire::IpProtocol; use smoltcp::wire::IpProtocol;
use std::cell::RefCell; use socks5_impl::protocol::{Address, UserKey};
use std::collections::hash_map::RandomState; use std::{
use std::collections::{HashMap, VecDeque}; cell::RefCell,
use std::iter::FromIterator; collections::{hash_map::RandomState, HashMap, VecDeque},
use std::net::SocketAddr; iter::FromIterator,
use std::rc::Rc; net::SocketAddr,
use std::str; rc::Rc,
str,
};
use unicase::UniCase; use unicase::UniCase;
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
@ -48,8 +51,8 @@ pub struct HttpConnection {
skip: usize, skip: usize,
digest_state: Rc<RefCell<Option<DigestState>>>, digest_state: Rc<RefCell<Option<DigestState>>>,
before: bool, before: bool,
credentials: Option<Credentials>, credentials: Option<UserKey>,
destination: Destination, destination: Address,
} }
static PROXY_AUTHENTICATE: &str = "Proxy-Authenticate"; static PROXY_AUTHENTICATE: &str = "Proxy-Authenticate";
@ -66,11 +69,11 @@ impl HttpConnection {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut res = Self { let mut res = Self {
state: HttpState::ExpectResponseHeaders, state: HttpState::ExpectResponseHeaders,
client_inbuf: Default::default(), client_inbuf: VecDeque::default(),
server_inbuf: Default::default(), server_inbuf: VecDeque::default(),
client_outbuf: Default::default(), client_outbuf: VecDeque::default(),
server_outbuf: Default::default(), server_outbuf: VecDeque::default(),
data_buf: Default::default(), data_buf: VecDeque::default(),
skip: 0, skip: 0,
counter: 0, counter: 0,
crlf_state: 0, crlf_state: 0,
@ -110,7 +113,7 @@ impl HttpConnection {
match scheme { match scheme {
AuthenticationScheme::Digest => { 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( let context = digest_auth::AuthContext::new_with_method(
&credentials.username, &credentials.username,
@ -386,7 +389,7 @@ impl TcpProxy for HttpConnection {
pub(crate) struct HttpManager { pub(crate) struct HttpManager {
server: SocketAddr, server: SocketAddr,
credentials: Option<Credentials>, credentials: Option<UserKey>,
digest_state: Rc<RefCell<Option<DigestState>>>, digest_state: Rc<RefCell<Option<DigestState>>>,
} }
@ -416,13 +419,13 @@ impl ConnectionManager for HttpManager {
self.server self.server
} }
fn get_credentials(&self) -> &Option<Credentials> { fn get_credentials(&self) -> &Option<UserKey> {
&self.credentials &self.credentials
} }
} }
impl HttpManager { impl HttpManager {
pub fn new(server: SocketAddr, credentials: Option<Credentials>) -> Rc<Self> { pub fn new(server: SocketAddr, credentials: Option<UserKey>) -> Rc<Self> {
Rc::new(Self { Rc::new(Self {
server, server,
credentials, credentials,

View file

@ -1,6 +1,8 @@
use crate::error::Error; use crate::{
use crate::socks::SocksVersion; error::Error, http::HttpManager, socks::SocksManager, socks::SocksVersion,
use crate::{http::HttpManager, socks::SocksManager, tun2proxy::TunToProxy}; tun2proxy::TunToProxy,
};
use socks5_impl::protocol::UserKey;
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};
mod android; mod android;
@ -16,7 +18,7 @@ mod virtdns;
pub struct Proxy { pub struct Proxy {
pub proxy_type: ProxyType, pub proxy_type: ProxyType,
pub addr: SocketAddr, pub addr: SocketAddr,
pub credentials: Option<Credentials>, pub credentials: Option<UserKey>,
} }
pub enum NetworkInterface { pub enum NetworkInterface {
@ -48,7 +50,7 @@ impl Proxy {
} else { } else {
let username = String::from(url.username()); let username = String::from(url.username());
let password = String::from(url.password().unwrap_or("")); let password = String::from(url.password().unwrap_or(""));
Some(Credentials::new(&username, &password)) Some(UserKey::new(username, password))
}; };
let scheme = url.scheme(); let scheme = url.scheme();
@ -94,7 +96,7 @@ pub struct Options {
impl Options { impl Options {
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Options::default()
} }
pub fn with_virtual_dns(mut self) -> Self { 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>( pub fn tun_to_proxy<'a>(
interface: &NetworkInterface, interface: &NetworkInterface,
proxy: &Proxy, proxy: &Proxy,

View file

@ -1,12 +1,7 @@
use clap::Parser; use clap::Parser;
use env_logger::Env; use env_logger::Env;
use std::{net::IpAddr, process::ExitCode};
use std::net::IpAddr; use tun2proxy::{error::Error, main_entry, NetworkInterface, Options, Proxy};
use std::process::ExitCode;
use tun2proxy::error::Error;
use tun2proxy::{main_entry, Proxy};
use tun2proxy::{NetworkInterface, Options};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use tun2proxy::setup::{get_default_cidrs, Setup}; use tun2proxy::setup::{get_default_cidrs, Setup};
@ -45,9 +40,9 @@ struct Args {
#[arg(short, long, value_name = "method", value_enum)] #[arg(short, long, value_name = "method", value_enum)]
setup: Option<ArgSetup>, setup: Option<ArgSetup>,
/// Public proxy IP used in routing setup /// Public proxy IP used in routing setup which should bypassing the tunnel
#[arg(long, value_name = "IP")] #[arg(long, value_name = "IP")]
setup_ip: Option<IpAddr>, bypass_ip: Option<IpAddr>,
} }
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] #[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")] #[cfg(target_os = "linux")]
{ {
let mut setup: Setup; let mut setup: Setup;
if args.setup == Some(ArgSetup::Auto) { 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, Some(addr) => addr,
None => args.proxy.addr.ip(), None => args.proxy.addr.ip(),
}; };
@ -96,7 +91,7 @@ fn main() -> ExitCode {
&args.tun, &args.tun,
&bypass_tun_ip, &bypass_tun_ip,
get_default_cidrs(), get_default_cidrs(),
args.setup_ip.is_some(), args.bypass_ip.is_some(),
); );
setup.configure()?; setup.configure()?;
@ -108,10 +103,11 @@ fn main() -> ExitCode {
main_entry(&interface, &args.proxy, options)?; main_entry(&interface, &args.proxy, options)?;
Ok(()) Ok(())
})() { };
if let Err(e) = block() {
log::error!("{e}"); log::error!("{e}");
return ExitCode::FAILURE; return ExitCode::FAILURE;
}; }
ExitCode::SUCCESS ExitCode::SUCCESS
} }

View file

@ -1,20 +1,17 @@
#![cfg(target_os = "linux")] #![cfg(target_os = "linux")]
use crate::error::Error; 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 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)] #[derive(Clone)]
pub struct Setup { pub struct Setup {
@ -320,7 +317,7 @@ impl Setup {
if self.tunnel_bypass_addr.is_loopback() && !self.allow_private { if self.tunnel_bypass_addr.is_loopback() && !self.allow_private {
log::warn!( log::warn!(
"The proxy address {} is a loopback address. You may need to manually \ "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 self.tunnel_bypass_addr
) )
} }

View file

@ -1,16 +1,13 @@
use std::collections::VecDeque; use crate::{
use std::convert::TryFrom; error::Error,
use std::net::{IpAddr, SocketAddr}; tun2proxy::{
use std::rc::Rc; Connection, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection,
OutgoingDataEvent, OutgoingDirection, TcpProxy,
use smoltcp::wire::IpProtocol; },
use crate::error::Error;
use crate::tun2proxy::{
Connection, ConnectionManager, DestinationHost, 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)] #[derive(Eq, PartialEq, Debug)]
#[allow(dead_code)] #[allow(dead_code)]
@ -24,32 +21,6 @@ enum SocksState {
Established, Established,
} }
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Debug)]
enum SocksAddressType {
Ipv4 = 1,
DomainName = 3,
Ipv6 = 4,
}
impl TryFrom<u8> for SocksAddressType {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(SocksAddressType::Ipv4),
3 => Ok(SocksAddressType::DomainName),
4 => Ok(SocksAddressType::Ipv6),
_ => Err(format!("Unknown address type: {}", value).into()),
}
}
}
impl From<SocksAddressType> for u8 {
fn from(value: SocksAddressType) -> Self {
value as u8
}
}
#[repr(u8)] #[repr(u8)]
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Debug)]
pub enum SocksVersion { pub enum SocksVersion {
@ -57,15 +28,6 @@ pub enum SocksVersion {
V5 = 5, V5 = 5,
} }
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Debug)]
#[allow(dead_code)]
pub enum SocksCommand {
Connect = 1,
Bind = 2,
UdpAssociate = 3,
}
#[allow(dead_code)] #[allow(dead_code)]
enum SocksAuthentication { enum SocksAuthentication {
None = 0, None = 0,
@ -105,7 +67,7 @@ pub(crate) struct SocksConnection {
server_outbuf: VecDeque<u8>, server_outbuf: VecDeque<u8>,
data_buf: VecDeque<u8>, data_buf: VecDeque<u8>,
version: SocksVersion, version: SocksVersion,
credentials: Option<Credentials>, credentials: Option<UserKey>,
} }
impl SocksConnection { impl SocksConnection {
@ -133,22 +95,20 @@ impl SocksConnection {
let credentials = &self.credentials; let credentials = &self.credentials;
match self.version { match self.version {
SocksVersion::V4 => { SocksVersion::V4 => {
self.server_outbuf.extend(&[ self.server_outbuf
self.version as u8, .extend(&[self.version as u8, protocol::Command::Connect.into()]);
SocksCommand::Connect as u8, self.server_outbuf
(self.connection.dst.port >> 8) as u8, .extend(self.connection.dst.port().to_be_bytes());
(self.connection.dst.port & 0xff) as u8,
]);
let mut ip_vec = Vec::<u8>::new(); let mut ip_vec = Vec::<u8>::new();
let mut name_vec = Vec::<u8>::new(); let mut name_vec = Vec::<u8>::new();
match &self.connection.dst.host { match &self.connection.dst {
DestinationHost::Address(dst_ip) => { Address::SocketAddress(SocketAddr::V4(addr)) => {
match dst_ip { ip_vec.extend(addr.ip().octets().as_ref());
IpAddr::V4(ip) => ip_vec.extend(ip.octets().as_ref()),
IpAddr::V6(_) => return Err("SOCKS4 does not support IPv6".into()),
};
} }
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]); ip_vec.extend(&[0, 0, 0, host.len() as u8]);
name_vec.extend(host.as_bytes()); name_vec.extend(host.as_bytes());
name_vec.push(0); name_vec.push(0);
@ -246,7 +206,7 @@ impl SocksConnection {
} }
fn send_auth_data(&mut self) -> Result<(), Error> { 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); let credentials = self.credentials.as_ref().unwrap_or(&tmp);
self.server_outbuf self.server_outbuf
.extend(&[1u8, credentials.username.len() as u8]); .extend(&[1u8, credentials.username.len() as u8]);
@ -287,8 +247,8 @@ impl SocksConnection {
return Err("SOCKS5 connection unsuccessful.".into()); return Err("SOCKS5 connection unsuccessful.".into());
} }
let message_length = match SocksAddressType::try_from(atyp)? { let message_length = match AddressType::try_from(atyp)? {
SocksAddressType::DomainName => { AddressType::Domain => {
if self.server_inbuf.len() < 5 { if self.server_inbuf.len() < 5 {
return Ok(()); return Ok(());
} }
@ -297,8 +257,8 @@ impl SocksConnection {
} }
7 + (self.server_inbuf[4] as usize) 7 + (self.server_inbuf[4] as usize)
} }
SocksAddressType::Ipv4 => 10, AddressType::IPv4 => 10,
SocksAddressType::Ipv6 => 22, AddressType::IPv6 => 22,
}; };
self.server_inbuf.drain(0..message_length); self.server_inbuf.drain(0..message_length);
@ -310,30 +270,8 @@ impl SocksConnection {
} }
fn send_request(&mut self) -> Result<(), Error> { fn send_request(&mut self) -> Result<(), Error> {
self.server_outbuf.extend(&[5u8, 1, 0]); protocol::Request::new(protocol::Command::Connect, self.connection.dst.clone())
match &self.connection.dst.host { .write_to_stream(&mut self.server_outbuf)?;
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,
]);
self.state = SocksState::ReceiveResponse; self.state = SocksState::ReceiveResponse;
self.state_change() self.state_change()
} }
@ -432,7 +370,7 @@ impl TcpProxy for SocksConnection {
pub struct SocksManager { pub struct SocksManager {
server: SocketAddr, server: SocketAddr,
credentials: Option<Credentials>, credentials: Option<UserKey>,
version: SocksVersion, version: SocksVersion,
} }
@ -462,7 +400,7 @@ impl ConnectionManager for SocksManager {
self.server self.server
} }
fn get_credentials(&self) -> &Option<Credentials> { fn get_credentials(&self) -> &Option<UserKey> {
&self.credentials &self.credentials
} }
} }
@ -471,7 +409,7 @@ impl SocksManager {
pub fn new( pub fn new(
server: SocketAddr, server: SocketAddr,
version: SocksVersion, version: SocksVersion,
credentials: Option<Credentials>, credentials: Option<UserKey>,
) -> Rc<Self> { ) -> Rc<Self> {
Rc::new(Self { Rc::new(Self {
server, server,

View file

@ -1,91 +1,35 @@
use crate::error::Error; use crate::{error::Error, virtdevice::VirtualTunDevice, NetworkInterface, Options};
use crate::virtdevice::VirtualTunDevice; use mio::{event::Event, net::TcpStream, unix::SourceFd, Events, Interest, Poll, Token};
use crate::{Credentials, NetworkInterface, Options}; use smoltcp::{
use log::{error, info}; iface::{Config, Interface, SocketHandle, SocketSet},
use mio::event::Event; phy::{Device, Medium, RxToken, TunTapInterface, TxToken},
use mio::net::TcpStream; socket::{tcp, tcp::State, udp, udp::UdpMetadata},
use mio::unix::SourceFd; time::Instant,
use mio::{Events, Interest, Poll, Token}; wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket},
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet}; };
use smoltcp::phy::{Device, Medium, RxToken, TunTapInterface, TxToken}; use socks5_impl::protocol::{Address, UserKey};
use smoltcp::socket::tcp::State; use std::{
use smoltcp::socket::udp::UdpMetadata; collections::{HashMap, HashSet},
use smoltcp::socket::{tcp, udp}; convert::{From, TryFrom},
use smoltcp::time::Instant; io::{Read, Write},
use smoltcp::wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket}; net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, Shutdown::Both, SocketAddr},
use std::collections::{HashMap, HashSet}; os::unix::io::AsRawFd,
use std::convert::{From, TryFrom}; rc::Rc,
use std::io::{Read, Write}; str::FromStr,
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<Destination> for SocketAddr {
type Error = Error;
fn try_from(value: Destination) -> Result<Self, Self::Error> {
let ip = match value.host {
DestinationHost::Address(addr) => addr,
DestinationHost::Hostname(e) => {
return Err(e.into());
}
};
Ok(SocketAddr::new(ip, value.port))
}
}
impl From<SocketAddr> 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)
}
}
}
#[derive(Hash, Clone, Eq, PartialEq, Debug)] #[derive(Hash, Clone, Eq, PartialEq, Debug)]
pub(crate) struct Connection { pub(crate) struct Connection {
pub(crate) src: SocketAddr, pub(crate) src: SocketAddr,
pub(crate) dst: Destination, pub(crate) dst: Address,
pub(crate) proto: IpProtocol, pub(crate) proto: IpProtocol,
} }
impl Connection { impl Connection {
fn to_named(&self, name: String) -> Self { fn to_named(&self, name: String) -> Self {
let mut result = self.clone(); 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 result
} }
} }
@ -160,7 +104,7 @@ fn connection_tuple(frame: &[u8]) -> Option<(Connection, bool, usize, usize)> {
if let Ok(packet) = Ipv4Packet::new_checked(frame) { if let Ok(packet) = Ipv4Packet::new_checked(frame) {
let proto = packet.next_header(); 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()); a.copy_from_slice(packet.src_addr().as_bytes());
let src_addr = IpAddr::from(a); let src_addr = IpAddr::from(a);
a.copy_from_slice(packet.dst_addr().as_bytes()); 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. // TODO: Support extension headers.
let proto = packet.next_header(); 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()); a.copy_from_slice(packet.src_addr().as_bytes());
let src_addr = IpAddr::from(a); let src_addr = IpAddr::from(a);
a.copy_from_slice(packet.dst_addr().as_bytes()); a.copy_from_slice(packet.dst_addr().as_bytes());
@ -241,7 +185,7 @@ pub(crate) trait ConnectionManager {
) -> Result<Option<Box<dyn TcpProxy>>, Error>; ) -> Result<Option<Box<dyn TcpProxy>>, Error>;
fn close_connection(&self, connection: &Connection); fn close_connection(&self, connection: &Connection);
fn get_server(&self) -> SocketAddr; fn get_server(&self) -> SocketAddr;
fn get_credentials(&self) -> &Option<Credentials>; fn get_credentials(&self) -> &Option<UserKey>;
} }
const TUN_TOKEN: Token = Token(0); const TUN_TOKEN: Token = Token(0);
@ -354,7 +298,7 @@ impl<'a> TunToProxy<'a> {
self.token_to_connection.remove(token); self.token_to_connection.remove(token);
self.sockets.remove(conn.smoltcp_handle); self.sockets.remove(conn.smoltcp_handle);
_ = self.poll.registry().deregister(&mut conn.mio_stream); _ = self.poll.registry().deregister(&mut conn.mio_stream);
info!("CLOSE {}", connection); log::info!("CLOSE {}", connection);
} }
Ok(()) Ok(())
} }
@ -479,9 +423,7 @@ impl<'a> TunToProxy<'a> {
// A raw packet was received on the tunnel interface. // A raw packet was received on the tunnel interface.
fn receive_tun(&mut self, frame: &mut [u8]) -> Result<(), Error> { fn receive_tun(&mut self, frame: &mut [u8]) -> Result<(), Error> {
if let Some((connection, first_packet, _payload_offset, _payload_size)) = if let Some((connection, first_packet, offset, size)) = connection_tuple(frame) {
connection_tuple(frame)
{
let resolved_conn = match &mut self.options.virtdns { let resolved_conn = match &mut self.options.virtdns {
None => connection.clone(), None => connection.clone(),
Some(virt_dns) => { Some(virt_dns) => {
@ -494,7 +436,7 @@ impl<'a> TunToProxy<'a> {
} }
}; };
let dst = connection.dst; let dst = connection.dst;
(|| -> Result<(), Error> { let handler = || -> Result<(), Error> {
if resolved_conn.proto == IpProtocol::Tcp { if resolved_conn.proto == IpProtocol::Tcp {
let cm = self.get_connection_manager(&resolved_conn); let cm = self.get_connection_manager(&resolved_conn);
if cm.is_none() { if cm.is_none() {
@ -540,7 +482,7 @@ impl<'a> TunToProxy<'a> {
self.connections.insert(resolved_conn.clone(), state); self.connections.insert(resolved_conn.clone(), state);
info!("CONNECT {}", resolved_conn,); log::info!("CONNECT {}", resolved_conn,);
break; break;
} }
} }
@ -562,9 +504,9 @@ impl<'a> TunToProxy<'a> {
// The connection handler builds up the connection or encapsulates the data. // The connection handler builds up the connection or encapsulates the data.
// Therefore, we now expect it to write data to the server. // Therefore, we now expect it to write data to the server.
self.write_to_server(&resolved_conn)?; self.write_to_server(&resolved_conn)?;
} else if resolved_conn.proto == IpProtocol::Udp && resolved_conn.dst.port == 53 { } else if resolved_conn.proto == IpProtocol::Udp && resolved_conn.dst.port() == 53 {
if let Some(virtual_dns) = &mut self.options.virtdns { if let Some(virtual_dns) = &mut self.options.virtdns {
let payload = &frame[_payload_offset.._payload_offset + _payload_size]; let payload = &frame[offset..offset + size];
if let Some(response) = virtual_dns.receive_query(payload) { if let Some(response) = virtual_dns.receive_query(payload) {
let rx_buffer = udp::PacketBuffer::new( let rx_buffer = udp::PacketBuffer::new(
vec![udp::PacketMetadata::EMPTY], vec![udp::PacketMetadata::EMPTY],
@ -590,12 +532,11 @@ impl<'a> TunToProxy<'a> {
} }
// Otherwise, UDP is not yet supported. // Otherwise, UDP is not yet supported.
} }
Ok(())
})()
.or_else(|error| {
log::error! {"{error}"}
Ok::<(), Error>(()) Ok::<(), Error>(())
})?; };
if let Err(error) = handler() {
log::error!("{}", error);
}
} }
Ok(()) Ok(())
} }
@ -709,7 +650,7 @@ impl<'a> TunToProxy<'a> {
.unwrap() .unwrap()
.get_server(); .get_server();
(|| -> Result<(), Error> { let mut block = || -> Result<(), Error> {
if event.is_readable() || event.is_read_closed() { if event.is_readable() || event.is_read_closed() {
{ {
let state = self.connections.get_mut(&connection).ok_or(e)?; let state = self.connections.get_mut(&connection).ok_or(e)?;
@ -721,7 +662,7 @@ impl<'a> TunToProxy<'a> {
Ok(read_result) => read_result, Ok(read_result) => read_result,
Err(error) => { Err(error) => {
if error.kind() != std::io::ErrorKind::WouldBlock { if error.kind() != std::io::ErrorKind::WouldBlock {
error!("Read from proxy: {}", error); log::error!("Read from proxy: {}", error);
} }
vecbuf.len() vecbuf.len()
} }
@ -752,7 +693,7 @@ impl<'a> TunToProxy<'a> {
// Closes the connection with the proxy // Closes the connection with the proxy
state.mio_stream.shutdown(Both)?; state.mio_stream.shutdown(Both)?;
info!("RESET {}", connection); log::info!("RESET {}", connection);
state.mio_stream = TcpStream::connect(server)?; state.mio_stream = TcpStream::connect(server)?;
@ -785,14 +726,13 @@ impl<'a> TunToProxy<'a> {
if event.is_writable() { if event.is_writable() {
self.write_to_server(&connection)?; self.write_to_server(&connection)?;
} }
Ok::<(), Error>(())
Ok(()) };
})() if let Err(error) = block() {
.or_else(|error| { log::error!("{}", error);
log::error! {"{error}"}
self.remove_connection(&connection)?; self.remove_connection(&connection)?;
Ok(()) }
}) Ok(())
} }
fn udp_event(&mut self, _event: &Event) {} fn udp_event(&mut self, _event: &Event) {}
@ -816,10 +756,10 @@ impl<'a> TunToProxy<'a> {
self.send_to_smoltcp()?; self.send_to_smoltcp()?;
} }
Err(e) => { Err(e) => {
if e.kind() != std::io::ErrorKind::Interrupted { if e.kind() == std::io::ErrorKind::Interrupted {
return Err(e.into()); log::warn!("Poll interrupted: \"{e}\", ignored, continue polling");
} else { } else {
log::warn!("Poll interrupted: {e}") return Err(e.into());
} }
} }
} }

View file

@ -1,6 +1,7 @@
use smoltcp::phy; use smoltcp::{
use smoltcp::phy::{Device, DeviceCapabilities}; phy::{self, Device, DeviceCapabilities},
use smoltcp::time::Instant; time::Instant,
};
#[derive(Default)] #[derive(Default)]
pub struct VirtualTunDevice { pub struct VirtualTunDevice {
@ -72,7 +73,7 @@ impl VirtualTunDevice {
pub fn new(capabilities: DeviceCapabilities) -> Self { pub fn new(capabilities: DeviceCapabilities) -> Self {
Self { Self {
capabilities, capabilities,
..Default::default() ..VirtualTunDevice::default()
} }
} }
} }

View file

@ -1,11 +1,12 @@
use hashlink::linked_hash_map::RawEntryMut; use hashlink::{linked_hash_map::RawEntryMut, LruCache};
use hashlink::LruCache;
use smoltcp::wire::Ipv4Cidr; use smoltcp::wire::Ipv4Cidr;
use std::collections::HashMap; use std::{
use std::convert::{TryFrom, TryInto}; collections::HashMap,
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; convert::{TryFrom, TryInto},
use std::str::FromStr; net::{IpAddr, Ipv4Addr, Ipv6Addr},
use std::time::{Duration, Instant}; str::FromStr,
time::{Duration, Instant},
};
const DNS_TTL: u8 = 30; // TTL in DNS replies in seconds const DNS_TTL: u8 = 30; // TTL in DNS replies in seconds
const MAPPING_TIMEOUT: u64 = 60; // Mapping timeout in seconds const MAPPING_TIMEOUT: u64 = 60; // Mapping timeout in seconds
@ -43,7 +44,7 @@ impl Default for VirtualDns {
Self { Self {
next_addr: start_addr.into(), next_addr: start_addr.into(),
name_to_ip: Default::default(), name_to_ip: HashMap::default(),
network_addr: IpAddr::try_from(cidr.network().address().into_address()).unwrap(), network_addr: IpAddr::try_from(cidr.network().address().into_address()).unwrap(),
broadcast_addr: IpAddr::try_from(cidr.broadcast().unwrap().into_address()).unwrap(), broadcast_addr: IpAddr::try_from(cidr.broadcast().unwrap().into_address()).unwrap(),
lru_cache: LruCache::new_unbounded(), lru_cache: LruCache::new_unbounded(),
@ -53,7 +54,7 @@ impl Default for VirtualDns {
impl VirtualDns { impl VirtualDns {
pub fn new() -> Self { pub fn new() -> Self {
Default::default() VirtualDns::default()
} }
pub fn receive_query(&mut self, data: &[u8]) -> Option<Vec<u8>> { pub fn receive_query(&mut self, data: &[u8]) -> Option<Vec<u8>> {
@ -66,18 +67,17 @@ impl VirtualDns {
// bit 7: Message is not truncated (0) // bit 7: Message is not truncated (0)
// bit 8: Recursion desired (1) // bit 8: Recursion desired (1)
let is_supported_query = (data[2] & 0b11111011) == 0b00000001; let is_supported_query = (data[2] & 0b11111011) == 0b00000001;
let num_queries = (data[4] as u16) << 8 | data[5] as u16; let num_queries = u16::from_be_bytes(data[4..6].try_into().ok()?);
if !is_supported_query || num_queries != 1 { if !is_supported_query || num_queries != 1 {
return None; return None;
} }
let result = VirtualDns::parse_qname(data, 12); let (qname, offset) = VirtualDns::parse_qname(data, 12)?;
let (qname, offset) = result?;
if offset + 3 >= data.len() { if offset + 3 >= data.len() {
return None; return None;
} }
let qtype = (data[offset] as u16) << 8 | data[offset + 1] as u16; let qtype = u16::from_be_bytes(data[offset..offset + 2].try_into().ok()?);
let qclass = (data[offset + 2] as u16) << 8 | data[offset + 3] as u16; let qclass = u16::from_be_bytes(data[offset + 2..offset + 4].try_into().ok()?);
if qtype != DnsRecordType::A as u16 && qtype != DnsRecordType::AAAA as u16 if qtype != DnsRecordType::A as u16 && qtype != DnsRecordType::AAAA as u16
|| qclass != DnsClass::IN as u16 || qclass != DnsClass::IN as u16
@ -121,7 +121,7 @@ impl VirtualDns {
0, 0, 0, DNS_TTL, // TTL 0, 0, 0, DNS_TTL, // TTL
0, 4, // Data length: 4 bytes 0, 4, // Data length: 4 bytes
]); ]);
match ip as IpAddr { match ip {
IpAddr::V4(ip) => response.extend(ip.octets().as_ref()), IpAddr::V4(ip) => response.extend(ip.octets().as_ref()),
IpAddr::V6(ip) => response.extend(ip.octets().as_ref()), IpAddr::V6(ip) => response.extend(ip.octets().as_ref()),
}; };
@ -191,11 +191,10 @@ impl VirtualDns {
let now = Instant::now(); let now = Instant::now();
loop { loop {
let p = self.lru_cache.iter().next(); let (ip, entry) = match self.lru_cache.iter().next() {
if p.is_none() { None => break,
break; Some((ip, entry)) => (ip, entry),
} };
let (ip, entry) = p.unwrap();
if now > entry.expiry { if now > entry.expiry {
let name = entry.name.clone(); let name = entry.name.clone();
self.lru_cache.remove(&ip.clone()); self.lru_cache.remove(&ip.clone());