use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{Context, Result};
use clap::Parser;
use log::{debug, info, warn};
use tokio::net::UdpSocket;
use shadowvpn::config::{ClientArgs, ClientConfig, TunConfig};
use shadowvpn::crypto::{decrypt_packet, encrypt_packet, Cipher};
use shadowvpn::obfs::{self, Obfuscator};
use shadowvpn::protocol::{max_datagram_size, MAX_IP_PACKET};
use shadowvpn::tun_device::TunDevice;
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(25);
const KEEPALIVE_PAYLOAD: &[u8] = &[0u8];
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let cfg = ClientArgs::parse()
.resolve()
.context("failed to resolve client configuration")?;
run(cfg).await
}
async fn run(cfg: ClientConfig) -> Result<()> {
let cipher = cfg.cipher;
let master_key: Arc<[u8]> = Arc::from(cfg.master_key.into_boxed_slice());
let obfuscator: Option<Arc<Obfuscator>> = cfg
.obfs
.as_deref()
.and_then(Obfuscator::from_name)
.map(Arc::new);
if let Some(name) = cfg.obfs.as_deref() {
info!("carrier obfuscation: {name}");
}
let socket = UdpSocket::bind(("0.0.0.0", 0))
.await
.context("failed to bind local UDP socket")?;
socket
.connect(&cfg.server)
.await
.with_context(|| format!("failed to connect UDP socket to server {}", cfg.server))?;
let direct_src = socket
.local_addr()
.map(|a| a.ip())
.unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED));
let local_addr = socket
.local_addr()
.map(|a| a.to_string())
.unwrap_or_else(|_| "<unknown>".to_string());
info!("UDP socket {local_addr} connected to server {}", cfg.server);
let socket = Arc::new(socket);
let tun = TunDevice::create(&cfg.tun).with_context(|| {
format!(
"failed to create TUN device (need root/elevated privileges); \
requested ip={} peer={} mtu={}",
cfg.tun.ip, cfg.tun.peer_ip, cfg.tun.mtu
)
})?;
let tun = Arc::new(tun);
let iface_name = tun.name().unwrap_or_else(|_| {
cfg.tun
.name
.clone()
.unwrap_or_else(|| "<unknown>".to_string())
});
info!(
"TUN up: iface={iface_name} ip={} peer={} netmask={} mtu={}",
cfg.tun.ip, cfg.tun.peer_ip, cfg.tun.netmask, cfg.tun.mtu
);
let mut policy_handle = if cfg.policy.mode.is_enabled() {
info!(
"policy routing mode = {}; only matched destinations are tunneled",
cfg.policy.mode.name()
);
Some(
shadowvpn::policy::spawn(&cfg.policy, &iface_name, cfg.tun.ip, direct_src)
.await
.context("failed to start policy routing")?,
)
} else {
print_routing_hint(&cfg.tun, &cfg.server);
None
};
let up = tokio::spawn(tun_to_net(
Arc::clone(&tun),
Arc::clone(&socket),
cipher,
Arc::clone(&master_key),
obfuscator.clone(),
));
let down = tokio::spawn(net_to_tun(
Arc::clone(&tun),
Arc::clone(&socket),
cipher,
Arc::clone(&master_key),
obfuscator.clone(),
));
let keepalive = tokio::spawn(keepalive_loop(
Arc::clone(&socket),
cipher,
Arc::clone(&master_key),
obfuscator.clone(),
));
let policy_fut = async {
if let Some(handle) = policy_handle.as_mut() {
return match (&mut handle.task).await {
Ok(inner) => inner.context("DNS proxy loop failed"),
Err(join) => Err(anyhow::Error::new(join).context("DNS proxy task panicked")),
};
}
std::future::pending::<Result<()>>().await
};
tokio::pin!(policy_fut);
tokio::select! {
r = up => propagate("tun->net", r),
r = down => propagate("net->tun", r),
r = keepalive => propagate("keepalive", r),
r = &mut policy_fut => r,
_ = shutdown_signal() => { info!("received shutdown signal; shutting down"); Ok(()) }
}
}
#[cfg(unix)]
async fn shutdown_signal() {
use tokio::signal::unix::{signal, SignalKind};
match signal(SignalKind::terminate()) {
Ok(mut term) => {
tokio::select! {
_ = tokio::signal::ctrl_c() => {}
_ = term.recv() => {}
}
}
Err(_) => {
let _ = tokio::signal::ctrl_c().await;
}
}
}
#[cfg(windows)]
async fn shutdown_signal() {
use tokio::signal::windows;
let mut close = windows::ctrl_close().expect("install ctrl-close handler");
let mut shutdown = windows::ctrl_shutdown().expect("install ctrl-shutdown handler");
tokio::select! {
_ = tokio::signal::ctrl_c() => {}
_ = close.recv() => {}
_ = shutdown.recv() => {}
}
}
fn propagate(which: &str, joined: Result<Result<()>, tokio::task::JoinError>) -> Result<()> {
match joined {
Ok(inner) => inner.with_context(|| format!("{which} loop failed")),
Err(join_err) => {
Err(anyhow::Error::new(join_err).context(format!("{which} task panicked/aborted")))
}
}
}
async fn tun_to_net(
tun: Arc<TunDevice>,
socket: Arc<UdpSocket>,
cipher: Cipher,
master_key: Arc<[u8]>,
obfuscator: Option<Arc<Obfuscator>>,
) -> Result<()> {
let mut buf = vec![0u8; MAX_IP_PACKET];
loop {
let n = tun
.recv(&mut buf)
.await
.context("failed to read from TUN device")?;
if n == 0 {
continue;
}
let datagram = match encrypt_packet(cipher, &master_key, &buf[..n]) {
Ok(d) => d,
Err(e) => {
warn!("failed to encrypt a {n}-byte packet, dropping: {e}");
continue;
}
};
let wire = match obfuscator {
Some(ref o) => o.wrap(&datagram),
None => datagram,
};
if let Err(e) = socket.send(&wire).await {
return Err(e).context("failed to send datagram to server");
}
debug!(
"tun->net: {n} bytes plaintext -> {} bytes on wire",
wire.len()
);
}
}
async fn net_to_tun(
tun: Arc<TunDevice>,
socket: Arc<UdpSocket>,
cipher: Cipher,
master_key: Arc<[u8]>,
obfuscator: Option<Arc<Obfuscator>>,
) -> Result<()> {
let mut buf = vec![0u8; max_datagram_size(cipher) + obfs::MAX_HEADER];
loop {
let n = match socket.recv(&mut buf).await {
Ok(n) => n,
Err(e) => return Err(e).context("failed to receive datagram from server"),
};
let decoded;
let datagram: &[u8] = match obfuscator {
Some(ref o) => match o.unwrap(&buf[..n]) {
Some(inner) => {
decoded = inner;
&decoded
}
None => {
debug!("dropping {n}-byte non-obfs datagram");
continue;
}
},
None => &buf[..n],
};
let plaintext = match decrypt_packet(cipher, &master_key, datagram) {
Ok(p) => p,
Err(e) => {
debug!("dropping undecryptable {n}-byte datagram: {e}");
continue;
}
};
if plaintext.len() < 20 {
debug!("dropping {}-byte sub-IP-header payload", plaintext.len());
continue;
}
if let Err(e) = tun.send(&plaintext).await {
return Err(e).context("failed to write packet to TUN device");
}
debug!(
"net->tun: {n} bytes datagram -> {} bytes plaintext",
plaintext.len()
);
}
}
async fn keepalive_loop(
socket: Arc<UdpSocket>,
cipher: Cipher,
master_key: Arc<[u8]>,
obfuscator: Option<Arc<Obfuscator>>,
) -> Result<()> {
let mut ticker = tokio::time::interval(KEEPALIVE_INTERVAL);
ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
ticker.tick().await;
let datagram = match encrypt_packet(cipher, &master_key, KEEPALIVE_PAYLOAD) {
Ok(d) => d,
Err(e) => {
warn!("failed to encrypt keepalive, skipping: {e}");
continue;
}
};
let wire = match obfuscator {
Some(ref o) => o.wrap(&datagram),
None => datagram,
};
if let Err(e) = socket.send(&wire).await {
return Err(e).context("failed to send keepalive to server");
}
debug!("sent {}-byte keepalive", wire.len());
}
}
fn print_routing_hint(tun: &TunConfig, server: &str) {
let peer = tun.peer_ip;
let local = tun.ip;
info!("-----------------------------------------------------------------");
info!("Tunnel is up (local {local}, peer {peer}). It does NOT change your");
info!("routing table. To send traffic through the tunnel, add routes by hand.");
info!("");
let server_host = server.rsplit_once(':').map(|(h, _)| h).unwrap_or(server);
let server_ip = server_host.parse::<Ipv4Addr>().ok();
#[cfg(target_os = "linux")]
{
info!("Linux:");
if let Some(ip) = server_ip {
info!(" # keep the server reachable over your real link (replace GW/DEV):");
info!(" sudo ip route add {ip}/32 via <YOUR_DEFAULT_GW> dev <YOUR_WAN_DEV>");
} else {
info!(" # first add a host route for the server's resolved IP via your real");
info!(" # gateway, so encrypted UDP does not re-enter the tunnel.");
}
info!(" # then route everything (or a subnet) through the tunnel peer:");
info!(" sudo ip route add 0.0.0.0/1 via {peer}");
info!(" sudo ip route add 128.0.0.0/1 via {peer}");
info!(" # (the two /1 routes override the default without deleting it)");
}
#[cfg(target_os = "macos")]
{
info!("macOS:");
if let Some(ip) = server_ip {
info!(" # keep the server reachable over your real link (replace GW):");
info!(" sudo route -n add -host {ip} <YOUR_DEFAULT_GW>");
} else {
info!(" # first add a host route for the server's resolved IP via your real");
info!(" # gateway, so encrypted UDP does not re-enter the tunnel.");
}
info!(" # then route everything through the tunnel peer:");
info!(" sudo route -n add -net 0.0.0.0/1 {peer}");
info!(" sudo route -n add -net 128.0.0.0/1 {peer}");
}
#[cfg(windows)]
{
info!("Windows (run in an elevated prompt):");
if let Some(ip) = server_ip {
info!(" :: keep the server reachable over your real link (replace GW):");
info!(" route add {ip} mask 255.255.255.255 <YOUR_DEFAULT_GW>");
} else {
info!(" :: first add a host route for the server's resolved IP via your real");
info!(" :: gateway, so encrypted UDP does not re-enter the tunnel.");
}
info!(" :: then route everything through the tunnel peer:");
info!(" route add 0.0.0.0 mask 128.0.0.0 {peer}");
info!(" route add 128.0.0.0 mask 128.0.0.0 {peer}");
}
#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
{
let _ = server_ip;
info!("Add a host route to the server via your real gateway, then route the");
info!("desired destinations via the tunnel peer {peer}.");
}
info!("");
info!("To stop using the tunnel, delete the routes you added above.");
info!("-----------------------------------------------------------------");
if server_ip.is_none() {
warn!(
"server '{server}' is a hostname, not a literal IP: resolve it and add a \
host route for that IP via your real gateway before routing all traffic."
);
}
}