#![cfg(target_os = "linux")]
mod helpers;
use std::{
os::unix::{io::AsRawFd, io::FromRawFd, net::UnixDatagram},
sync::{atomic::AtomicU32, Arc},
time::Duration,
};
use nix::{
sched::CloneFlags,
sys::wait::{waitpid, WaitStatus},
unistd::{Gid, Uid},
};
use proxychains_masq::{
chain::{ChainConfig, ChainType, ProxyEntry, ProxyType},
dns::DnsMap,
tunnel::{ProxyChainTunnel, TunnelConfig},
};
use sendfd::{RecvWithFd, SendWithFd};
use smoltcp::phy::{Medium, TunTapInterface};
fn ip(args: &[&str]) -> bool {
std::process::Command::new("ip")
.args(args)
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn child_run(sock: UnixDatagram, uid: Uid, gid: Gid, echo_port: u16) -> isize {
if std::fs::write("/proc/self/setgroups", "deny\n").is_err()
|| std::fs::write("/proc/self/uid_map", format!("0 {uid} 1\n")).is_err()
|| std::fs::write("/proc/self/gid_map", format!("0 {gid} 1\n")).is_err()
{
eprintln!("child: failed to write user namespace maps");
return 1;
}
let tun = match TunTapInterface::new("tun0", Medium::Ip) {
Ok(t) => t,
Err(e) => {
eprintln!("child: TunTapInterface::new failed: {e}");
return 1;
}
};
if sock.send_with_fd(&[0u8], &[tun.as_raw_fd()]).is_err() {
eprintln!("child: send_with_fd failed");
return 1;
}
if !ip(&["addr", "add", "127.0.0.1/8", "dev", "lo"]) || !ip(&["link", "set", "lo", "up"]) {
eprintln!("child: lo setup failed");
return 1;
}
if !ip(&["addr", "add", "10.99.0.1/30", "dev", "tun0"])
|| !ip(&["link", "set", "tun0", "up"])
|| !ip(&["route", "add", "default", "dev", "tun0"])
{
eprintln!("child: tun0 setup failed");
return 1;
}
let mut buf = [0u8; 1];
if sock.recv(&mut buf).is_err() {
eprintln!("child: recv ready signal failed");
return 1;
}
let addr = format!("100.0.0.1:{echo_port}");
let stream = match std::net::TcpStream::connect(&addr) {
Ok(s) => s,
Err(e) => {
eprintln!("child: connect {addr} failed: {e}");
return 1;
}
};
stream
.set_read_timeout(Some(std::time::Duration::from_secs(10)))
.ok();
use std::io::{Read, Write};
let mut s = stream;
if s.write_all(b"hello-tun").is_err() {
eprintln!("child: write failed");
return 1;
}
let mut resp = [0u8; 9];
if s.read_exact(&mut resp).is_err() {
eprintln!("child: read_exact failed");
return 1;
}
if &resp != b"hello-tun" {
eprintln!("child: wrong echo: {resp:?}");
return 1;
}
drop(tun);
0
}
fn run_tunnel_test(fake_hostname: &str) -> WaitStatus {
let rt = tokio::runtime::Runtime::new().unwrap();
let (echo_port, _echo_hits) = rt.block_on(helpers::echo_server());
let socks_hits = Arc::new(AtomicU32::new(0));
let socks_port = rt.block_on(helpers::socks5_server(None, socks_hits.clone()));
let (parent_sock, child_sock) = UnixDatagram::pair().unwrap();
let uid = Uid::current();
let gid = Gid::current();
let mut stack = vec![0u8; 4 * 1024 * 1024];
let mut child_sock_opt = Some(child_sock);
let child_pid = unsafe {
nix::sched::clone(
Box::new(move || {
let sock = child_sock_opt
.take()
.expect("clone callback called more than once");
child_run(sock, uid, gid, echo_port)
}),
&mut stack,
CloneFlags::CLONE_NEWNET | CloneFlags::CLONE_NEWUSER,
Some(nix::libc::SIGCHLD),
)
.expect("clone(2) failed — is kernel.unprivileged_userns_clone=1?")
};
let mut dummy = [0u8; 1];
let mut raw_fds = [-1i32; 1];
parent_sock
.recv_with_fd(&mut dummy, &mut raw_fds)
.expect("recv_with_fd");
assert!(raw_fds[0] >= 0, "received valid TUN fd from child");
{
use nix::fcntl::{fcntl, FcntlArg, OFlag};
let current = OFlag::from_bits_truncate(fcntl(raw_fds[0], FcntlArg::F_GETFL).unwrap());
fcntl(raw_fds[0], FcntlArg::F_SETFL(current | OFlag::O_NONBLOCK)).unwrap();
}
let tun_file = unsafe { std::fs::File::from_raw_fd(raw_fds[0]) };
let dns_map = DnsMap::new(100);
dns_map.get_or_alloc(fake_hostname).unwrap();
let config = TunnelConfig {
chain: ChainConfig {
chain_type: ChainType::Strict,
proxies: vec![ProxyEntry {
proxy_type: ProxyType::Socks5,
addr: "127.0.0.1".parse().unwrap(),
port: socks_port,
username: None,
password: None,
}],
chain_len: 1,
chain_retries: 3,
connect_timeout: Duration::from_secs(8),
localnets: vec![],
dnats: vec![],
tls_skip_verify: false,
proxy_dead_threshold: 0,
},
dns_map,
tun_ip: "10.99.0.2".parse().unwrap(), prefix_len: 30,
dns_ip: None,
};
let tunnel = ProxyChainTunnel::new(config);
let _thread = tunnel.spawn(tun_file, rt.handle().clone());
parent_sock.send(&[1u8]).unwrap();
waitpid(child_pid, None).unwrap()
}
#[test]
fn test_tunnel_relays_tcp() {
match run_tunnel_test("127.0.0.1") {
WaitStatus::Exited(_, 0) => {}
other => panic!("child failed: {other:?}"),
}
}
#[test]
fn test_flow_resolves_fake_ip_to_hostname() {
match run_tunnel_test("localhost") {
WaitStatus::Exited(_, 0) => {}
other => panic!("child failed: {other:?}"),
}
}