mirror of
https://github.com/tun2proxy/tun2proxy.git
synced 2025-04-19 13:29:09 +00:00
Allow multiple bypass IP addresses/CIDRs in routing setup
See issue #73.
This commit is contained in:
parent
9b27dd2df2
commit
e08a0f683d
10 changed files with 112 additions and 123 deletions
13
Dockerfile
13
Dockerfile
|
@ -12,18 +12,9 @@ RUN cargo build --release --target x86_64-unknown-linux-gnu
|
|||
## Final image
|
||||
####################################################################################################
|
||||
FROM ubuntu:latest
|
||||
WORKDIR /app
|
||||
|
||||
ENV TUN=tun0
|
||||
ENV PROXY=
|
||||
ENV DNS=virtual
|
||||
ENV MODE=auto
|
||||
ENV BYPASS_IP=
|
||||
ENV VERBOSITY=info
|
||||
|
||||
RUN apt update && apt install -y iproute2 curl && apt clean all
|
||||
RUN apt update && apt install -y iproute2 && apt clean all
|
||||
|
||||
COPY --from=builder /worker/target/x86_64-unknown-linux-gnu/release/tun2proxy /usr/bin/tun2proxy
|
||||
COPY --from=builder /worker/docker/entrypoint.sh /app
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
ENTRYPOINT ["/usr/bin/tun2proxy", "--setup", "auto"]
|
||||
|
|
18
README.md
18
README.md
|
@ -99,7 +99,7 @@ Options:
|
|||
--dns-addr <IP> DNS resolver address [default: 8.8.8.8]
|
||||
-6, --ipv6-enabled IPv6 enabled
|
||||
-s, --setup <method> Routing and system setup [default: none] [possible values: none, auto]
|
||||
-b, --bypass <IP> Public proxy IP used in routing setup which should bypassing the tunnel
|
||||
-b, --bypass <IP|CIDR> IPs and CIDRs used in routing setup which should bypass the tunnel
|
||||
-v, --verbosity <level> Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace]
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
|
@ -119,31 +119,17 @@ Next, start a container from the tun2proxy image:
|
|||
|
||||
```bash
|
||||
docker run -d \
|
||||
-e PROXY=proto://[username[:password]@]host:port \
|
||||
-v /dev/net/tun:/dev/net/tun \
|
||||
--sysctl net.ipv6.conf.default.disable_ipv6=0 \
|
||||
--cap-add NET_ADMIN \
|
||||
--name tun2proxy \
|
||||
tun2proxy
|
||||
tun2proxy --proxy proto://[username[:password]@]host:port
|
||||
```
|
||||
|
||||
container env list
|
||||
|
||||
| container env | Default | program option | mean |
|
||||
| ------------- | ------- | ----------------------- | ------------------------------------------------------------ |
|
||||
| TUN | tun0 | -t, --tun <name> | Name of the tun interface [default: tun0] |
|
||||
| PROXY | None | -p, --proxy <URL> | Proxy URL in the form proto://[username[:password]@]host:port |
|
||||
| DNS | virtual | -d, --dns <strategy> | DNS handling strategy [default: virtual] [possible values: virtual, over-tcp, direct] |
|
||||
| MODE | auto | -s, --setup <method> | Routing and system setup [default: none] [possible values: none, auto] |
|
||||
| BYPASS_IP | None | -b, --bypass <IP> | Public proxy IP used in routing setup which should bypassing the tunnel |
|
||||
| VERBOSITY | info | -v, --verbosity <level> | Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] |
|
||||
| | | | |
|
||||
|
||||
You can then provide the running container's network to another worker container by sharing the network namespace (like kubernetes sidecar):
|
||||
|
||||
```bash
|
||||
docker run -it \
|
||||
-d \
|
||||
--network "container:tun2proxy" \
|
||||
ubuntu:latest
|
||||
```
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
run() {
|
||||
if [ -n "$TUN" ]; then
|
||||
TUN="--tun $TUN"
|
||||
fi
|
||||
|
||||
if [ -n "$PROXY" ]; then
|
||||
PROXY="--proxy $PROXY"
|
||||
fi
|
||||
|
||||
if [ -n "$DNS" ]; then
|
||||
DNS="--dns $DNS"
|
||||
fi
|
||||
|
||||
if [ -n "$BYPASS_IP" ]; then
|
||||
BYPASS_IP="--bypass $BYPASS_IP"
|
||||
fi
|
||||
|
||||
if [ -n "$VERBOSITY" ]; then
|
||||
VERBOSITY="-v $VERBOSITY"
|
||||
fi
|
||||
|
||||
if [ -n "$MODE" ]; then
|
||||
MODE="--setup $MODE"
|
||||
fi
|
||||
|
||||
echo "Bootstrap ready!! Exec Command: tun2proxy $TUN $PROXY $DNS $VERBOSITY $MODE $BYPASS_IP $@"
|
||||
|
||||
exec tun2proxy $TUN $PROXY $DNS $VERBOSITY $MODE $BYPASS_IP $@
|
||||
}
|
||||
|
||||
|
||||
run $@ || echo "Runing ERROR!!"
|
10
src/lib.rs
10
src/lib.rs
|
@ -4,6 +4,7 @@ use crate::{
|
|||
socks::SocksProxyManager,
|
||||
tun2proxy::{ConnectionManager, TunToProxy},
|
||||
};
|
||||
use smoltcp::wire::IpCidr;
|
||||
use socks5_impl::protocol::UserKey;
|
||||
use std::{
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
|
@ -17,6 +18,7 @@ mod http;
|
|||
pub mod setup;
|
||||
mod socks;
|
||||
mod tun2proxy;
|
||||
pub mod util;
|
||||
mod virtdevice;
|
||||
mod virtdns;
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -104,8 +106,8 @@ pub struct Options {
|
|||
dns_over_tcp: bool,
|
||||
dns_addr: Option<std::net::IpAddr>,
|
||||
ipv6_enabled: bool,
|
||||
bypass: Option<std::net::IpAddr>,
|
||||
pub setup: bool,
|
||||
bypass: Vec<IpCidr>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
|
@ -140,8 +142,10 @@ impl Options {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_bypass(mut self, ip: Option<std::net::IpAddr>) -> Self {
|
||||
self.bypass = ip;
|
||||
pub fn with_bypass_ips<'a>(mut self, bypass_ips: impl IntoIterator<Item = &'a IpCidr>) -> Self {
|
||||
for bypass_ip in bypass_ips {
|
||||
self.bypass.push(*bypass_ip);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -1,5 +1,7 @@
|
|||
use clap::Parser;
|
||||
use smoltcp::wire::IpCidr;
|
||||
use std::{net::IpAddr, process::ExitCode};
|
||||
use tun2proxy::util::str_to_cidr;
|
||||
use tun2proxy::{error::Error, main_entry, NetworkInterface, Options, Proxy};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -41,9 +43,9 @@ struct Args {
|
|||
#[arg(short, long, value_name = "method", value_enum, default_value = if cfg!(target_os = "linux") { "none" } else { "auto" })]
|
||||
setup: Option<ArgSetup>,
|
||||
|
||||
/// Public proxy IP used in routing setup which should bypassing the tunnel
|
||||
#[arg(short, long, value_name = "IP")]
|
||||
bypass: Option<IpAddr>,
|
||||
/// IPs used in routing setup which should bypass the tunnel
|
||||
#[arg(short, long, value_name = "IP|CIDR")]
|
||||
bypass: Vec<String>,
|
||||
|
||||
/// Verbosity level
|
||||
#[arg(short, long, value_name = "level", value_enum, default_value = "info")]
|
||||
|
@ -53,7 +55,7 @@ struct Args {
|
|||
/// 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
|
||||
/// - Direct: Do not handle DNS by relying on DNS server bypassing
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
||||
enum ArgDns {
|
||||
Virtual,
|
||||
|
@ -117,21 +119,29 @@ fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
let bypass_tun_ip = match args.bypass {
|
||||
Some(addr) => addr,
|
||||
None => args.proxy.addr.ip(),
|
||||
};
|
||||
options = options.with_bypass(Some(bypass_tun_ip));
|
||||
|
||||
options.setup = args.setup.map(|s| s == ArgSetup::Auto).unwrap_or(false);
|
||||
|
||||
let block = || -> Result<(), Error> {
|
||||
let mut bypass_ips = Vec::<IpCidr>::new();
|
||||
for cidr_str in args.bypass {
|
||||
bypass_ips.push(str_to_cidr(&cidr_str)?);
|
||||
}
|
||||
if bypass_ips.is_empty() {
|
||||
let prefix_len = if args.proxy.addr.ip().is_ipv6() { 128 } else { 32 };
|
||||
bypass_ips.push(IpCidr::new(args.proxy.addr.ip().into(), prefix_len))
|
||||
}
|
||||
|
||||
options = options.with_bypass_ips(&bypass_ips);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let mut setup: Setup;
|
||||
if options.setup {
|
||||
let mut setup = Setup::new(&args.tun, &bypass_tun_ip, get_default_cidrs(), args.bypass.is_some());
|
||||
setup = Setup::new(&args.tun, bypass_ips, get_default_cidrs());
|
||||
setup.configure()?;
|
||||
setup.drop_privileges()?;
|
||||
}
|
||||
}
|
||||
|
||||
main_entry(&interface, &args.proxy, options)?;
|
||||
|
||||
|
|
73
src/setup.rs
73
src/setup.rs
|
@ -8,7 +8,7 @@ use std::{
|
|||
ffi::OsStr,
|
||||
fs,
|
||||
io::BufRead,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
os::unix::io::RawFd,
|
||||
process::{Command, Output},
|
||||
str::FromStr,
|
||||
|
@ -17,11 +17,10 @@ use std::{
|
|||
#[derive(Clone)]
|
||||
pub struct Setup {
|
||||
routes: Vec<IpCidr>,
|
||||
tunnel_bypass_addr: IpAddr,
|
||||
allow_private: bool,
|
||||
tunnel_bypass_addrs: Vec<IpCidr>,
|
||||
tun: String,
|
||||
set_up: bool,
|
||||
delete_proxy_route: bool,
|
||||
delete_proxy_routes: Vec<IpCidr>,
|
||||
child: libc::pid_t,
|
||||
unmount_resolvconf: bool,
|
||||
restore_resolvconf_data: Option<Vec<u8>>,
|
||||
|
@ -76,35 +75,41 @@ where
|
|||
impl Setup {
|
||||
pub fn new(
|
||||
tun: impl Into<String>,
|
||||
tunnel_bypass_addr: &IpAddr,
|
||||
tunnel_bypass_addrs: impl IntoIterator<Item = IpCidr>,
|
||||
routes: impl IntoIterator<Item = IpCidr>,
|
||||
allow_private: bool,
|
||||
) -> Self {
|
||||
let routes_cidr = routes.into_iter().collect();
|
||||
let bypass_cidrs = tunnel_bypass_addrs.into_iter().collect();
|
||||
Self {
|
||||
tun: tun.into(),
|
||||
tunnel_bypass_addr: *tunnel_bypass_addr,
|
||||
allow_private,
|
||||
tunnel_bypass_addrs: bypass_cidrs,
|
||||
routes: routes_cidr,
|
||||
set_up: false,
|
||||
delete_proxy_route: false,
|
||||
delete_proxy_routes: Vec::<IpCidr>::new(),
|
||||
child: 0,
|
||||
unmount_resolvconf: false,
|
||||
restore_resolvconf_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn route_proxy_address(&mut self) -> Result<bool, Error> {
|
||||
let route_show_args = if self.tunnel_bypass_addr.is_ipv6() {
|
||||
fn bypass_cidr(cidr: &IpCidr) -> Result<bool, Error> {
|
||||
let is_ipv6 = match cidr {
|
||||
IpCidr::Ipv4(_) => false,
|
||||
IpCidr::Ipv6(_) => true,
|
||||
};
|
||||
let route_show_args = if is_ipv6 {
|
||||
["ip", "-6", "route", "show"]
|
||||
} else {
|
||||
["ip", "-4", "route", "show"]
|
||||
};
|
||||
|
||||
let routes = run_iproute(route_show_args, "failed to get routing table", true)?;
|
||||
let routes = run_iproute(
|
||||
route_show_args,
|
||||
"failed to get routing table through the ip command",
|
||||
true,
|
||||
)?;
|
||||
|
||||
let mut route_info = Vec::<(IpCidr, Vec<String>)>::new();
|
||||
|
||||
for line in routes.stdout.lines() {
|
||||
if line.is_err() {
|
||||
break;
|
||||
|
@ -117,15 +122,11 @@ impl Setup {
|
|||
let mut split = line.split_whitespace();
|
||||
let mut dst_str = split.next().unwrap();
|
||||
if dst_str == "default" {
|
||||
dst_str = if self.tunnel_bypass_addr.is_ipv6() {
|
||||
"::/0"
|
||||
} else {
|
||||
"0.0.0.0/0"
|
||||
}
|
||||
dst_str = if is_ipv6 { "::/0" } else { "0.0.0.0/0" }
|
||||
}
|
||||
|
||||
let (addr_str, prefix_len_str) = match dst_str.split_once(['/']) {
|
||||
None => (dst_str, if self.tunnel_bypass_addr.is_ipv6() { "128" } else { "32" }),
|
||||
None => (dst_str, if is_ipv6 { "128" } else { "32" }),
|
||||
Some((addr_str, prefix_len_str)) => (addr_str, prefix_len_str),
|
||||
};
|
||||
|
||||
|
@ -140,19 +141,19 @@ impl Setup {
|
|||
// Sort routes by prefix length, the most specific route comes first.
|
||||
route_info.sort_by(|entry1, entry2| entry2.0.prefix_len().cmp(&entry1.0.prefix_len()));
|
||||
|
||||
for (cidr, route_components) in route_info {
|
||||
if !cidr.contains_addr(&smoltcp::wire::IpAddress::from(self.tunnel_bypass_addr)) {
|
||||
for (route_cidr, route_components) in route_info {
|
||||
if !route_cidr.contains_subnet(cidr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The IP address is routed through a more specific route than the default route.
|
||||
// In this case, there is nothing to do.
|
||||
if cidr.prefix_len() != 0 {
|
||||
if route_cidr.prefix_len() != 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut proxy_route = vec!["ip".into(), "route".into(), "add".into()];
|
||||
proxy_route.push(self.tunnel_bypass_addr.to_string());
|
||||
proxy_route.push(cidr.to_string());
|
||||
proxy_route.extend(route_components.into_iter());
|
||||
run_iproute(proxy_route, "failed to clone route for proxy", false)?;
|
||||
return Ok(true);
|
||||
|
@ -235,14 +236,17 @@ impl Setup {
|
|||
self.set_up = false;
|
||||
log::info!("[{}] Restoring network configuration", nix::unistd::getpid());
|
||||
let _ = Command::new("ip").args(["link", "del", self.tun.as_str()]).output();
|
||||
if self.delete_proxy_route {
|
||||
|
||||
for cidr in &self.delete_proxy_routes {
|
||||
let _ = Command::new("ip")
|
||||
.args(["route", "del", self.tunnel_bypass_addr.to_string().as_str()])
|
||||
.args(["route", "del", cidr.to_string().as_str()])
|
||||
.output();
|
||||
}
|
||||
|
||||
if self.unmount_resolvconf {
|
||||
nix::mount::umount("/etc/resolv.conf")?;
|
||||
}
|
||||
|
||||
if let Some(data) = &self.restore_resolvconf_data {
|
||||
fs::write("/etc/resolv.conf", data)?;
|
||||
}
|
||||
|
@ -259,8 +263,6 @@ impl Setup {
|
|||
)?;
|
||||
|
||||
self.set_up = true;
|
||||
let _tun_name = self.tun.clone();
|
||||
let _proxy_ip = self.tunnel_bypass_addr;
|
||||
|
||||
run_iproute(
|
||||
["ip", "link", "set", self.tun.as_str(), "up"],
|
||||
|
@ -268,8 +270,13 @@ impl Setup {
|
|||
true,
|
||||
)?;
|
||||
|
||||
let delete_proxy_route = self.route_proxy_address()?;
|
||||
self.delete_proxy_route = delete_proxy_route;
|
||||
let mut delete_proxy_route = Vec::<IpCidr>::new();
|
||||
for cidr in &self.tunnel_bypass_addrs {
|
||||
if Self::bypass_cidr(cidr)? {
|
||||
delete_proxy_route.push(*cidr);
|
||||
}
|
||||
}
|
||||
self.delete_proxy_routes = delete_proxy_route;
|
||||
self.setup_resolv_conf()?;
|
||||
self.add_tunnel_routes()?;
|
||||
|
||||
|
@ -321,14 +328,6 @@ impl Setup {
|
|||
return Err("Automatic setup requires root privileges".into());
|
||||
}
|
||||
|
||||
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 --bypass-ip to specify the server IP bypassing the tunnel",
|
||||
self.tunnel_bypass_addr
|
||||
)
|
||||
}
|
||||
|
||||
let (read_from_child, write_to_parent) = nix::unistd::pipe()?;
|
||||
match fork::fork() {
|
||||
Ok(Fork::Child) => {
|
||||
|
|
|
@ -259,7 +259,7 @@ impl<'a> TunToProxy<'a> {
|
|||
|
||||
#[cfg(target_os = "windows")]
|
||||
if options.setup {
|
||||
tun.setup_config(options.bypass, options.dns_addr)?;
|
||||
tun.setup_config(&options.bypass, options.dns_addr)?;
|
||||
}
|
||||
|
||||
let poll = Poll::new()?;
|
||||
|
|
22
src/util.rs
Normal file
22
src/util.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use crate::error::Error;
|
||||
use smoltcp::wire::IpCidr;
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn str_to_cidr(s: &str) -> Result<IpCidr, Error> {
|
||||
// IpCidr's FromString implementation requires the netmask to be specified.
|
||||
// Try to parse as IP address without netmask before falling back.
|
||||
match IpAddr::from_str(s) {
|
||||
Err(_) => (),
|
||||
Ok(cidr) => {
|
||||
let prefix_len = if cidr.is_ipv4() { 32 } else { 128 };
|
||||
return Ok(IpCidr::new(cidr.into(), prefix_len));
|
||||
}
|
||||
};
|
||||
|
||||
let cidr = IpCidr::from_str(s);
|
||||
match cidr {
|
||||
Err(()) => Err("Invalid CIDR: ".into()),
|
||||
Ok(cidr) => Ok(cidr),
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use mio::{event, windows::NamedPipe, Interest, Registry, Token};
|
||||
use smoltcp::wire::IpCidr;
|
||||
use smoltcp::{
|
||||
phy::{self, Device, DeviceCapabilities, Medium},
|
||||
time::Instant,
|
||||
|
@ -225,7 +226,11 @@ impl WinTunInterface {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_config(&mut self, bypass_ip: Option<IpAddr>, dns_addr: Option<IpAddr>) -> Result<(), io::Error> {
|
||||
pub fn setup_config<'a>(
|
||||
&mut self,
|
||||
bypass_ips: impl IntoIterator<Item = &'a IpCidr>,
|
||||
dns_addr: Option<IpAddr>,
|
||||
) -> Result<(), io::Error> {
|
||||
let adapter = self.wintun_session.get_adapter();
|
||||
|
||||
// Setup the adapter's address/mask/gateway
|
||||
|
@ -261,7 +266,7 @@ impl WinTunInterface {
|
|||
|
||||
// 3. route the bypass ip to the old gateway
|
||||
// command: `route add bypass_ip old_gateway metric 1`
|
||||
if let Some(bypass_ip) = bypass_ip {
|
||||
for bypass_ip in bypass_ips {
|
||||
let args = &["add", &bypass_ip.to_string(), &old_gateway.to_string(), "metric", "1"];
|
||||
run_command("route", args)?;
|
||||
log::info!("route {:?}", args);
|
||||
|
|
|
@ -11,8 +11,10 @@ mod tests {
|
|||
use nix::sys::signal;
|
||||
use nix::unistd::Pid;
|
||||
use serial_test::serial;
|
||||
use smoltcp::wire::IpCidr;
|
||||
|
||||
use tun2proxy::setup::{get_default_cidrs, Setup};
|
||||
use tun2proxy::util::str_to_cidr;
|
||||
use tun2proxy::{main_entry, NetworkInterface, Options, Proxy, ProxyType};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -66,12 +68,17 @@ mod tests {
|
|||
continue;
|
||||
}
|
||||
|
||||
let bypass_ip = match env::var("BYPASS_IP") {
|
||||
Err(_) => test.proxy.addr.ip(),
|
||||
Ok(ip_str) => IpAddr::from_str(ip_str.as_str()).unwrap(),
|
||||
let mut bypass_ips = Vec::<IpCidr>::new();
|
||||
|
||||
match env::var("BYPASS_IP") {
|
||||
Err(_) => {
|
||||
let prefix_len = if test.proxy.addr.ip().is_ipv6() { 128 } else { 32 };
|
||||
bypass_ips.push(IpCidr::new(test.proxy.addr.ip().into(), prefix_len));
|
||||
}
|
||||
Ok(ip_str) => bypass_ips.push(str_to_cidr(&ip_str).expect("Invalid bypass IP")),
|
||||
};
|
||||
|
||||
let mut setup = Setup::new(TUN_TEST_DEVICE, &bypass_ip, get_default_cidrs(), false);
|
||||
let mut setup = Setup::new(TUN_TEST_DEVICE, bypass_ips, get_default_cidrs());
|
||||
setup.configure().unwrap();
|
||||
|
||||
match fork::fork() {
|
||||
|
|
Loading…
Add table
Reference in a new issue