From b75c943714399677903ae31358430d3bbe43316b Mon Sep 17 00:00:00 2001 From: nullchinchilla Date: Sun, 23 Jun 2024 19:09:50 -0400 Subject: [PATCH 1/3] a kinda but not really working fake dns --- Cargo.lock | 10 ++ binaries/geph5-client/Cargo.toml | 1 + binaries/geph5-client/src/client.rs | 95 +----------- binaries/geph5-client/src/client_inner.rs | 18 ++- binaries/geph5-client/src/vpn.rs | 170 ++++++++++++++++++++++ 5 files changed, 203 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea8b49f..a19d040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2449,6 +2449,7 @@ dependencies = [ "serde_yaml", "sillad", "sillad-sosistab3", + "simple-dns", "smol 2.0.0", "smol-timeout", "smol_str", @@ -5316,6 +5317,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simple-dns" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3625957337d21eb40a7125c2df5c92db5c0267195d66b297948c816ea9c33157" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "simplecss" version = "0.2.1" diff --git a/binaries/geph5-client/Cargo.toml b/binaries/geph5-client/Cargo.toml index 815dcb4..6ee1d1c 100644 --- a/binaries/geph5-client/Cargo.toml +++ b/binaries/geph5-client/Cargo.toml @@ -53,6 +53,7 @@ serde_json = "1.0.117" serde_yaml = "0.9.34" sillad = { path = "../../libraries/sillad" } sillad-sosistab3 = { path = "../../libraries/sillad-sosistab3" } +simple-dns = "0.7.0" smol = "2.0.0" smol-timeout = "0.6.0" smol_str = { version = "0.2.2", features = ["serde"] } diff --git a/binaries/geph5-client/src/client.rs b/binaries/geph5-client/src/client.rs index 3dc53b7..4fda6ca 100644 --- a/binaries/geph5-client/src/client.rs +++ b/binaries/geph5-client/src/client.rs @@ -23,13 +23,13 @@ use smolscale::immortal::{Immortal, RespawnStrategy}; use crate::{ auth::auth_loop, broker::{broker_client, BrokerSource}, - client_inner::{client_once, open_conn}, + client_inner::client_once, database::db_read_or_wait, http_proxy::run_http_proxy, route::ExitConstraint, socks5::socks5_loop, stats::stat_get_num, - vpn::VpnCapture, + vpn::vpn_loop, }; #[derive(Serialize, Deserialize, Clone)] @@ -44,6 +44,8 @@ pub struct Config { #[serde(default)] pub vpn: bool, #[serde(default)] + pub spoof_dns: bool, + #[serde(default)] pub dry_run: bool, #[serde(default)] pub credentials: Credential, @@ -138,94 +140,7 @@ async fn client_main(ctx: AnyCtx) -> anyhow::Result<()> { }) .await } else { - let vpn_loop = async { - if ctx.init().vpn { - let vpn = VpnCapture::new(ctx.clone()); - loop { - let captured = vpn - .ipstack() - .accept() - .await - .context("could not accept from ipstack")?; - match captured { - ipstack_geph::stream::IpStackStream::Tcp(captured) => { - let peer_addr = captured.peer_addr(); - tracing::trace!( - local_addr = display(captured.local_addr()), - peer_addr = display(peer_addr), - "captured a TCP" - ); - let ctx = ctx.clone(); - - smolscale::spawn(async move { - let tunneled = open_conn(&ctx, &peer_addr.to_string()).await?; - tracing::trace!( - peer_addr = display(peer_addr), - "dialed through VPN" - ); - let (read_tunneled, write_tunneled) = tunneled.split(); - let (read_captured, write_captured) = captured.split(); - smol::io::copy(read_tunneled, write_captured) - .race(smol::io::copy(read_captured, write_tunneled)) - .await?; - anyhow::Ok(()) - }) - .detach(); - } - ipstack_geph::stream::IpStackStream::Udp(captured) => { - let peer_addr = captured.peer_addr(); - tracing::trace!( - local_addr = display(captured.local_addr()), - peer_addr = display(peer_addr), - "captured a UDP" - ); - let peer_addr = if captured.peer_addr().port() == 53 { - "1.1.1.1:53".parse()? - } else { - peer_addr - }; - let ctx = ctx.clone(); - smolscale::spawn::>(async move { - let tunneled = open_conn(&ctx, &format!("udp${peer_addr}")).await?; - let (read_tunneled, write_tunneled) = tunneled.split(); - let up_loop = async { - let mut write_tunneled = BufWriter::new(write_tunneled); - loop { - let to_up = captured.recv().await?; - write_tunneled - .write_all(&(to_up.len() as u16).to_le_bytes()) - .await?; - write_tunneled.write_all(&to_up).await?; - write_tunneled.flush().await?; - } - }; - let dn_loop = async { - let mut read_tunneled = BufReader::new(read_tunneled); - loop { - let mut len_buf = [0u8; 2]; - read_tunneled.read_exact(&mut len_buf).await?; - let len = u16::from_le_bytes(len_buf) as usize; - let mut buf = vec![0u8; len]; - read_tunneled.read_exact(&mut buf).await?; - captured.send(&buf).await?; - } - }; - up_loop.race(dn_loop).await - }) - .detach(); - } - ipstack_geph::stream::IpStackStream::UnknownTransport(_) => { - tracing::warn!("captured an UnknownTransport") - } - ipstack_geph::stream::IpStackStream::UnknownNetwork(_) => { - tracing::warn!("captured an UnknownNetwork") - } - } - } - } else { - smol::future::pending().await - } - }; + let vpn_loop = vpn_loop(&ctx); let _client_loop = Immortal::respawn( RespawnStrategy::JitterDelay(Duration::from_secs(1), Duration::from_secs(5)), diff --git a/binaries/geph5-client/src/client_inner.rs b/binaries/geph5-client/src/client_inner.rs index 7c46ba2..edd446c 100644 --- a/binaries/geph5-client/src/client_inner.rs +++ b/binaries/geph5-client/src/client_inner.rs @@ -18,7 +18,8 @@ use sillad::{ use smol::future::FutureExt as _; use smol_timeout::TimeoutExt; use std::{ - net::SocketAddr, + net::{IpAddr, SocketAddr}, + str::FromStr, sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, Arc, @@ -33,11 +34,26 @@ use crate::{ client::CtxField, route::{deprioritize_route, get_dialer}, stats::stat_incr_num, + vpn::fake_dns_backtranslate, }; use super::Config; pub async fn open_conn(ctx: &AnyCtx, dest_addr: &str) -> anyhow::Result { + let dest_addr = if let Ok(sock_addr) = SocketAddr::from_str(dest_addr) { + if let IpAddr::V4(v4) = sock_addr.ip() { + if let Some(orig) = fake_dns_backtranslate(ctx, v4) { + format!("{orig}:{}", sock_addr.port()) + } else { + dest_addr.to_string() + } + } else { + dest_addr.to_string() + } + } else { + dest_addr.to_string() + }; + let (send, recv) = oneshot::channel(); let elem = (dest_addr.to_string(), send); let _ = ctx.get(CONN_REQ_CHAN).0.send(elem).await; diff --git a/binaries/geph5-client/src/vpn.rs b/binaries/geph5-client/src/vpn.rs index 5a9e632..5d73622 100644 --- a/binaries/geph5-client/src/vpn.rs +++ b/binaries/geph5-client/src/vpn.rs @@ -2,12 +2,28 @@ #[cfg(target_os = "linux")] mod linux; +use std::{ + net::{Ipv4Addr, SocketAddr}, + time::Duration, +}; + +use anyctx::AnyCtx; +use anyhow::Context; +use futures_util::{AsyncReadExt, AsyncWriteExt}; #[cfg(target_os = "linux")] pub use linux::*; #[cfg(target_os = "windows")] mod windows; +use moka::sync::Cache; +use pnet_packet::ipv4::Ipv4; +use rand::Rng; +use simple_dns::{Packet, QTYPE}; +use smol::{ + future::FutureExt, + io::{BufReader, BufWriter}, +}; #[cfg(target_os = "windows")] pub use windows::*; @@ -16,3 +32,157 @@ mod macos; #[cfg(target_os = "macos")] pub use macos::*; + +use crate::{client::CtxField, client_inner::open_conn, Config}; + +static FAKE_DNS_FORWARD: CtxField> = |_| { + Cache::builder() + .time_to_idle(Duration::from_secs(600)) + .build() +}; + +static FAKE_DNS_BACKWARD: CtxField> = |_| { + Cache::builder() + .time_to_idle(Duration::from_secs(600)) + .build() +}; + +pub fn fake_dns_backtranslate(ctx: &AnyCtx, fake: Ipv4Addr) -> Option { + tracing::debug!(fake = debug(fake), "attempting to backtranslate"); + ctx.get(FAKE_DNS_BACKWARD).get(&fake) +} + +pub fn fake_dns_allocate(ctx: &AnyCtx, dns_name: &str) -> Ipv4Addr { + ctx.get(FAKE_DNS_FORWARD) + .get_with(dns_name.to_string(), || { + let base = u32::from_be_bytes([100, 64, 0, 0]); + let mask = u32::from_be_bytes([255, 192, 0, 0]); + let offset = rand::thread_rng().gen_range(0..=!mask); + let ip_addr = base | offset; + let ip_addr = Ipv4Addr::from(ip_addr); + ctx.get(FAKE_DNS_BACKWARD) + .insert(ip_addr, dns_name.to_string()); + tracing::debug!( + from = debug(dns_name), + to = debug(ip_addr), + "created fake dns mapping", + ); + ip_addr + }) +} + +pub async fn vpn_loop(ctx: &AnyCtx) -> anyhow::Result<()> { + if ctx.init().vpn { + let vpn = VpnCapture::new(ctx.clone()); + loop { + let captured = vpn + .ipstack() + .accept() + .await + .context("could not accept from ipstack")?; + match captured { + ipstack_geph::stream::IpStackStream::Tcp(captured) => { + let peer_addr = captured.peer_addr(); + tracing::trace!( + local_addr = display(captured.local_addr()), + peer_addr = display(peer_addr), + "captured a TCP" + ); + let ctx = ctx.clone(); + + smolscale::spawn(async move { + let tunneled = open_conn(&ctx, &peer_addr.to_string()).await?; + tracing::trace!(peer_addr = display(peer_addr), "dialed through VPN"); + let (read_tunneled, write_tunneled) = tunneled.split(); + let (read_captured, write_captured) = captured.split(); + smol::io::copy(read_tunneled, write_captured) + .race(smol::io::copy(read_captured, write_tunneled)) + .await?; + anyhow::Ok(()) + }) + .detach(); + } + ipstack_geph::stream::IpStackStream::Udp(captured) => { + let peer_addr = captured.peer_addr(); + tracing::trace!( + local_addr = display(captured.local_addr()), + peer_addr = display(peer_addr), + "captured a UDP" + ); + let peer_addr = if captured.peer_addr().port() == 53 { + "1.1.1.1:53".parse()? + } else { + peer_addr + }; + + let ctx = ctx.clone(); + smolscale::spawn::>(async move { + if peer_addr.port() == 53 && ctx.init().spoof_dns { + // fakedns handling + loop { + let pkt = captured.recv().await?; + let pkt = Packet::parse(&pkt)?; + tracing::debug!(pkt = debug(&pkt), "got DNS packet"); + let mut answers = vec![]; + for question in pkt.questions.iter() { + if question.qtype == QTYPE::TYPE(simple_dns::TYPE::A) { + answers.push(simple_dns::ResourceRecord::new( + question.qname.clone(), + simple_dns::CLASS::IN, + 1, + simple_dns::rdata::RData::A( + fake_dns_allocate( + &ctx, + &question.qname.to_string(), + ) + .into(), + ), + )); + } + } + let mut response = Packet::new_reply(pkt.id()); + response.answers = answers; + captured.send(&response.build_bytes_vec()?).await?; + } + } else { + let tunneled = open_conn(&ctx, &format!("udp${peer_addr}")).await?; + let (read_tunneled, write_tunneled) = tunneled.split(); + let up_loop = async { + let mut write_tunneled = BufWriter::new(write_tunneled); + loop { + let to_up = captured.recv().await?; + write_tunneled + .write_all(&(to_up.len() as u16).to_le_bytes()) + .await?; + write_tunneled.write_all(&to_up).await?; + write_tunneled.flush().await?; + } + }; + let dn_loop = async { + let mut read_tunneled = BufReader::new(read_tunneled); + loop { + let mut len_buf = [0u8; 2]; + read_tunneled.read_exact(&mut len_buf).await?; + let len = u16::from_le_bytes(len_buf) as usize; + let mut buf = vec![0u8; len]; + read_tunneled.read_exact(&mut buf).await?; + captured.send(&buf).await?; + } + }; + up_loop.race(dn_loop).await + } + }) + .detach(); + } + ipstack_geph::stream::IpStackStream::UnknownTransport(_) => { + tracing::warn!("captured an UnknownTransport") + } + ipstack_geph::stream::IpStackStream::UnknownNetwork(_) => { + tracing::warn!("captured an UnknownNetwork") + } + } + } + } else { + smol::future::pending().await + } +} From c2e4b4824ff4b1fac9fa63fa105b2b8d2d33e770 Mon Sep 17 00:00:00 2001 From: nullchinchilla Date: Wed, 26 Jun 2024 17:19:12 -0400 Subject: [PATCH 2/3] fix --- binaries/geph5-client/src/vpn.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/binaries/geph5-client/src/vpn.rs b/binaries/geph5-client/src/vpn.rs index 5d73622..4ea81af 100644 --- a/binaries/geph5-client/src/vpn.rs +++ b/binaries/geph5-client/src/vpn.rs @@ -19,7 +19,7 @@ mod windows; use moka::sync::Cache; use pnet_packet::ipv4::Ipv4; use rand::Rng; -use simple_dns::{Packet, QTYPE}; +use simple_dns::{Packet, PacketFlag, QTYPE}; use smol::{ future::FutureExt, io::{BufReader, BufWriter}, @@ -37,18 +37,18 @@ use crate::{client::CtxField, client_inner::open_conn, Config}; static FAKE_DNS_FORWARD: CtxField> = |_| { Cache::builder() - .time_to_idle(Duration::from_secs(600)) + .time_to_live(Duration::from_secs(300)) .build() }; static FAKE_DNS_BACKWARD: CtxField> = |_| { Cache::builder() - .time_to_idle(Duration::from_secs(600)) + .time_to_live(Duration::from_secs(600)) .build() }; pub fn fake_dns_backtranslate(ctx: &AnyCtx, fake: Ipv4Addr) -> Option { - tracing::debug!(fake = debug(fake), "attempting to backtranslate"); + tracing::trace!(fake = debug(fake), "attempting to backtranslate"); ctx.get(FAKE_DNS_BACKWARD).get(&fake) } @@ -122,7 +122,7 @@ pub async fn vpn_loop(ctx: &AnyCtx) -> anyhow::Result<()> { loop { let pkt = captured.recv().await?; let pkt = Packet::parse(&pkt)?; - tracing::debug!(pkt = debug(&pkt), "got DNS packet"); + tracing::trace!(pkt = debug(&pkt), "got DNS packet"); let mut answers = vec![]; for question in pkt.questions.iter() { if question.qtype == QTYPE::TYPE(simple_dns::TYPE::A) { @@ -140,9 +140,12 @@ pub async fn vpn_loop(ctx: &AnyCtx) -> anyhow::Result<()> { )); } } - let mut response = Packet::new_reply(pkt.id()); + let mut response = pkt.into_reply(); response.answers = answers; - captured.send(&response.build_bytes_vec()?).await?; + + captured + .send(&response.build_bytes_vec_compressed()?) + .await?; } } else { let tunneled = open_conn(&ctx, &format!("udp${peer_addr}")).await?; From abdf0e140eb09af8615360a306a0341073526e43 Mon Sep 17 00:00:00 2001 From: nullchinchilla Date: Wed, 26 Jun 2024 18:08:01 -0400 Subject: [PATCH 3/3] remove mandatory death --- binaries/geph5-client-gui/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/binaries/geph5-client-gui/src/main.rs b/binaries/geph5-client-gui/src/main.rs index acbdbb6..8c02333 100644 --- a/binaries/geph5-client-gui/src/main.rs +++ b/binaries/geph5-client-gui/src/main.rs @@ -192,12 +192,11 @@ impl eframe::App for App { let _ = native_dialog::MessageDialog::new() .set_title("Fatal error") .set_text(&format!( - "Unfortunately, a fatal error occurred, so Geph must die:\n\n{:?}", + "Unfortunately, a fatal error occurred:\n\n{:?}", err )) .set_type(MessageType::Error) .show_alert(); - std::process::exit(-1); } }