diff --git a/Cargo.lock b/Cargo.lock index abdf289..42cfca9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -839,6 +839,26 @@ dependencies = [ "serde", ] +[[package]] +name = "c2rust-bitfields" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b43c3f07ab0ef604fa6f595aa46ec2f8a22172c975e186f6f5bf9829a3b72c41" +dependencies = [ + "c2rust-bitfields-derive", +] + +[[package]] +name = "c2rust-bitfields-derive" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3cbc102e2597c9744c8bd8c15915d554300601c91a079430d309816b0912545" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cache-padded" version = "1.3.0" @@ -1741,7 +1761,7 @@ dependencies = [ "egui", "glow", "log", - "memoffset", + "memoffset 0.9.1", "wasm-bindgen", "web-sys", "winit", @@ -1823,6 +1843,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "etherparse" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "095ab548cf452be5813424558a18af88f0a620d0f4a3d8793aa09311a3b6fa5f" +dependencies = [ + "arrayvec", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -2262,6 +2291,7 @@ dependencies = [ "anyctx", "anyhow", "async-compat", + "async-dup", "async-trait", "atomic_float", "blake3", @@ -2281,6 +2311,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "ipstack-geph", "isahc", "isocountry", "mizaru2", @@ -2314,6 +2345,7 @@ dependencies = [ "tower-service", "tracing", "tracing-subscriber", + "tun", "x25519-dalek", ] @@ -2342,6 +2374,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "single-instance", "smol 2.0.0", "smol-timeout", "smol_str", @@ -2608,7 +2641,7 @@ dependencies = [ "libc", "libloading 0.8.3", "thiserror", - "widestring", + "widestring 1.1.0", "winapi", ] @@ -2815,7 +2848,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -2930,12 +2963,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ioctl-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c" + [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "ipstack-geph" +version = "0.1.0" +dependencies = [ + "ahash", + "anyhow", + "async-channel 2.3.1", + "async-executor", + "async-io 2.3.2", + "bytes", + "etherparse", + "futures-lite 2.3.0", + "log", + "parking_lot 0.12.2", + "pollster", + "rand", + "thiserror", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -3282,6 +3340,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -3499,6 +3566,19 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + [[package]] name = "no-std-compat" version = "0.4.1" @@ -4968,6 +5048,19 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "single-instance" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4637485391f8545c9d3dbf60f9d9aab27a90c789a700999677583bcb17c8795d" +dependencies = [ + "libc", + "nix", + "thiserror", + "widestring 0.4.3", + "winapi", +] + [[package]] name = "slab" version = "0.4.9" @@ -5928,6 +6021,19 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" +[[package]] +name = "tun" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adb9992bbd5ca76f3847ed579ad4ee8defb2ec2eea918cceef17ccc66fa4fd4" +dependencies = [ + "ioctl-sys", + "libc", + "log", + "thiserror", + "wintun", +] + [[package]] name = "type-map" version = "0.5.0" @@ -6463,6 +6569,12 @@ dependencies = [ "wasite", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + [[package]] name = "widestring" version = "1.1.0" @@ -6509,16 +6621,35 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core 0.51.1", + "windows-targets 0.48.5", +] + [[package]] name = "windows" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core", + "windows-core 0.52.0", "windows-targets 0.52.5", ] +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -6802,6 +6933,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "wintun" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b83b0eca06dd125dbcd48a45327c708a6da8aada3d95a3f06db0ce4b17e0d4" +dependencies = [ + "c2rust-bitfields", + "libloading 0.8.3", + "log", + "thiserror", + "windows 0.51.1", +] + [[package]] name = "x11-dl" version = "2.21.0" diff --git a/binaries/geph5-client-gui/Cargo.toml b/binaries/geph5-client-gui/Cargo.toml index 8db2270..e48a38e 100644 --- a/binaries/geph5-client-gui/Cargo.toml +++ b/binaries/geph5-client-gui/Cargo.toml @@ -36,6 +36,7 @@ http = "1.1.0" isocountry = "0.3.2" image = { version = "0.25.1", default-features = false, features = ["ico"] } itertools = "0.13.0" +single-instance = "0.3.3" [build-dependencies] winresource = "0.1" diff --git a/binaries/geph5-client-gui/src/l10n.csv b/binaries/geph5-client-gui/src/l10n.csv index 0f0e693..ef9b9d5 100644 --- a/binaries/geph5-client-gui/src/l10n.csv +++ b/binaries/geph5-client-gui/src/l10n.csv @@ -2,26 +2,20 @@ label,en,zh,ru,fa about,About,关于,О программе,Darbāre account_info,Account Info,帐户信息,Информация об аккаунте,Eṭṭelā'āt-e ḥesāb auto,Auto,自动,Авто,Xodkār +broker,Broker server,Broker服务器,Брокерский сервер,Serveur de courtier +broker_direct,Direct,直连,Прямой,Direct +broker_direct_tcp,Direct (TCP),直连(TCP),Прямой (TCP),Direct (TCP) +broker_fronted,Fronted,前置,Передний,Façade +broker_fronted_front,Front,前置,Фронт-сервер,Frontal +broker_fronted_host,Host,主机名,Имя хоста,Nom d'hôte +broker_none,Default,默认,По умолчанию,Défaut cancel,Cancel,取消,Отмена,Lagv connect,Connect,连接,Подключить,Etesāl connected,Connected,已连接,Подключено,Mottasel connection_time,Connection time,连接时间,Время соединения,Zamān-e etesāl -exit,Exit,退出,Выход,Koruj -exit_location,Exit location,出口位置,Выходная точка,Makān-e xoroj -geph,Geph,迷雾通,Геф,Gef -help,Help,帮助,Помощь,Rāhnamā -language,Language,语言,Язык,Zabān -loading,Loading,加载中,Загрузка,Dar hāl-e bārgozārī -loading_exit_list,Loading exit list...,正在加载出口列表...,Загрузка списка выходов...,Dar hāl-e bārgozārī-ye liste xuruji-hā -login,Login,登录,Вход,Vorūd -logout,Logout,登出,Выход,Az vorūd khārej shodan -logs,Logs,日志,Журналы,Lāg-hā -network_settings,Network Settings,网络设置,Настройки сети,Tanzimāt-e šabake -ok,OK,确定,ОК,Tayīd -password,Password,密码,Пароль,Ramz-e 'obur country_ar,Argentina,阿根廷,Аргентина,Argentīn -country_au,Australia,澳大利亚,Австралия,Ostūrālīyā country_at,Austria,奥地利,Австрия,Otrīsh +country_au,Australia,澳大利亚,Австралия,Ostūrālīyā country_be,Belgium,比利时,Бельгия,Beljīk country_br,Brazil,巴西,Бразилия,Brazīl country_ca,Canada,加拿大,Канада,Kānādā @@ -74,13 +68,23 @@ disconnect,Disconnect,断开连接,Отключить,Qat'-e etesāl disconnected,Disconnected,已断开连接,Отключено,Qat' šode ast download_speed,Download speed,下载速度,Скорость загрузки,Sor'at-e dānlod exit,Exit,退出,Выход,Koruj +exit,Exit,退出,Выход,Koruj exit_location,Exit location,出口位置,Выходная точка,Makān-e xoroj geph,Geph,迷雾通,Геф,Gef +geph,Geph,迷雾通,Геф,Gef +geph_already_running,Geph is already running,Geph 已在运行,Geph уже запущен,Geph dar ḥāl-e ejrā ast +help,Help,帮助,Помощь,Rāhnamā help,Help,帮助,Помощь,Rāhnamā +http_proxy_port,HTTP proxy port,HTTP代理端口,HTTP-прокси-порт,HTTP proxy port language,Language,语言,Язык,Zabān +language,Language,语言,Язык,Zabān +loading,Loading,加载中,Загрузка,Dar hāl-e bārgozārī loading,Loading,加载中,Загрузка,Dar hāl-e bārgozārī loading_exit_list,Loading exit list...,正在加载出口列表...,Загрузка списка выходов...,Dar hāl-e bārgozārī-ye liste xuruji-hā login,Login,登录,Вход,Vorūd +login,Login,登录,Вход,Vorūd +logout,Logout,登出,Выход,Az vorūd khārej shodan +logs,Logs,日志,Журналы,Lāg-hā logs,Logs,日志,Журналы,Lāg-hā network_settings,Network Settings,网络设置,Настройки сети,Tanzimāt-e šabake ok,OK,确定,ОК,Tayīd @@ -92,16 +96,8 @@ save,Save,保存,Сохранить,Zaxīre selected_server,Selected Server,选定的服务器,Выбранный сервер,Sarvar-e entexābī server,Server,服务器,Сервер,Sarvar settings,Settings,设置,Настройки,Tanzimāt +socks5_port,Socks5 proxy port,Socks5代理端口,Порт прокси Socks5,Socks5 proxy port status,Status,状态,Статус,Vazīyat upload_speed,Upload Speed,上传速度,Скорость отдачи,Sor'at-e āplod username,Username,用户名,Имя пользователя,Nām-e karbarī -zoom_factor,Zoom Factor,缩放,Масштабирование,Zarīb-e bozorg-namā'ī -broker,Broker server,Broker服务器,Брокерский сервер,Serveur de courtier -broker_none,Default,默认,По умолчанию,Défaut -broker_direct,Direct,直连,Прямой,Direct -broker_fronted,Fronted,前置,Передний,Façade -broker_direct_tcp,Direct (TCP),直连(TCP),Прямой (TCP),Direct (TCP) -broker_fronted_front,Front,前置,Фронт-сервер,Frontal -broker_fronted_host,Host,主机名,Имя хоста,Nom d'hôte -socks5_port,Socks5 proxy port,Socks5代理端口,Порт прокси Socks5,Socks5 proxy port -http_proxy_port,HTTP proxy port,HTTP代理端口,HTTP-прокси-порт,HTTP proxy port \ No newline at end of file +zoom_factor,Zoom Factor,缩放,Масштабирование,Zarīb-e bozorg-namā'ī \ No newline at end of file diff --git a/binaries/geph5-client-gui/src/main.rs b/binaries/geph5-client-gui/src/main.rs index 38d6de0..71b0a83 100644 --- a/binaries/geph5-client-gui/src/main.rs +++ b/binaries/geph5-client-gui/src/main.rs @@ -19,12 +19,23 @@ use logs::LogLayer; use native_dialog::MessageType; use prefs::{pref_read, pref_write}; use settings::USERNAME; +use single_instance::SingleInstance; use tabs::{dashboard::Dashboard, login::Login, logs::Logs, settings::render_settings}; use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt, EnvFilter}; // 0123456789 fn main() { + let instance = SingleInstance::new("geph5-client-gui").unwrap(); + if !instance.is_single() { + native_dialog::MessageDialog::new() + .set_type(MessageType::Error) + .set_text(l10n("geph_already_running")) + .set_title("Error") + .show_alert() + .unwrap(); + std::process::exit(-1) + } tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() diff --git a/binaries/geph5-client/Cargo.toml b/binaries/geph5-client/Cargo.toml index a62aa05..209b0ce 100644 --- a/binaries/geph5-client/Cargo.toml +++ b/binaries/geph5-client/Cargo.toml @@ -8,6 +8,7 @@ license = "MPL-2.0" anyctx = "0.1.0" anyhow = "1.0.86" async-compat = "0.2.3" +async-dup = "1.2.4" async-trait = "0.1.80" atomic_float = "1.0.0" blake3 = "1.5.1" @@ -27,6 +28,8 @@ http = "1.1.0" http-body-util = "0.1.1" hyper = { version = "1.3.1", features = ["http1", "client", "server"] } hyper-util = { version = "0.1.3", features = ["http1", "client", "server"] } +# ipstack-geph = "0.1.0" +ipstack-geph={path="../../../ipstack-geph"} isahc = "1.7.2" isocountry = "0.3.2" mizaru2 = { path = "../../libraries/mizaru2" } @@ -60,6 +63,7 @@ tokio = { version = "1.37.0", features = ["rt", "net", "io-util"] } tower-service = "0.3.2" tracing = "0.1.40" tracing-subscriber = "0.3.18" +tun = "0.6.1" x25519-dalek = "2.0.1" [target.'cfg(unix)'.dependencies] diff --git a/binaries/geph5-client/src/client.rs b/binaries/geph5-client/src/client.rs index 15f34d4..1e1f772 100644 --- a/binaries/geph5-client/src/client.rs +++ b/binaries/geph5-client/src/client.rs @@ -1,6 +1,8 @@ use anyctx::AnyCtx; use clone_macro::clone; -use futures_util::{future::Shared, task::noop_waker, FutureExt, TryFutureExt}; +use futures_util::{ + future::Shared, task::noop_waker, AsyncReadExt, AsyncWriteExt, FutureExt, TryFutureExt, +}; use geph5_broker_protocol::{Credential, ExitList}; use smol::future::FutureExt as _; use std::{ @@ -17,12 +19,13 @@ use smolscale::immortal::{Immortal, RespawnStrategy}; use crate::{ auth::auth_loop, broker::{broker_client, BrokerSource}, - client_inner::client_once, + client_inner::{client_once, open_conn}, database::db_read_or_wait, http_proxy::run_http_proxy, route::ExitConstraint, socks5::socks5_loop, stats::stat_get_num, + vpn::VpnCapture, }; #[derive(Serialize, Deserialize, Clone)] @@ -121,6 +124,56 @@ async fn client_main(ctx: AnyCtx) -> anyhow::Result<()> { }) .await } else { + let vpn = VpnCapture::new(); + + let vpn_loop = async { + loop { + let captured = vpn.ipstack().accept().await?; + match captured { + ipstack_geph::stream::IpStackStream::Tcp(captured) => { + let peer_addr = captured.peer_addr(); + tracing::warn!( + local_addr = display(captured.local_addr()), + peer_addr = display(peer_addr), + "captured a TCP" + ); + let ctx = ctx.clone(); + let (mut read, mut write) = captured.split(); + smolscale::spawn(async move { + let mut lala = [0u8; 1000]; + loop { + let _ = read.read(&mut lala).await?; + } + anyhow::Ok(()) + }) + .detach(); + smolscale::spawn(async move { + loop { + tracing::warn!("GONNA WRITE"); + write.write_all(b"testTEST12345").await?; + } + // let tunneled = open_conn(&ctx, &peer_addr.to_string()).await?; + // tracing::debug!(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(_) => tracing::warn!("captured a UDP"), + ipstack_geph::stream::IpStackStream::UnknownTransport(_) => { + tracing::warn!("captured an UnknownTransport") + } + ipstack_geph::stream::IpStackStream::UnknownNetwork(_) => { + tracing::warn!("captured an UnknownNetwork") + } + } + } + }; + let _client_loop = Immortal::respawn( RespawnStrategy::JitterDelay(Duration::from_secs(1), Duration::from_secs(5)), clone!([ctx], move || client_once(ctx.clone()).inspect_err( @@ -128,6 +181,7 @@ async fn client_main(ctx: AnyCtx) -> anyhow::Result<()> { )), ); socks5_loop(&ctx) + .race(vpn_loop) .race(run_http_proxy(&ctx)) .race(auth_loop(&ctx)) .await diff --git a/binaries/geph5-client/src/lib.rs b/binaries/geph5-client/src/lib.rs index 6f58ea0..3544e30 100644 --- a/binaries/geph5-client/src/lib.rs +++ b/binaries/geph5-client/src/lib.rs @@ -13,3 +13,4 @@ mod http_proxy; mod route; mod socks5; mod stats; +mod vpn; diff --git a/binaries/geph5-client/src/vpn.rs b/binaries/geph5-client/src/vpn.rs new file mode 100644 index 0000000..9588213 --- /dev/null +++ b/binaries/geph5-client/src/vpn.rs @@ -0,0 +1,82 @@ +//! This module provides functionality for setting up a system-level VPN. + +use std::{ + net::{IpAddr, Ipv4Addr}, + os::fd::FromRawFd, +}; + +use anyhow::Context; +use bytes::Bytes; +use futures_util::{AsyncReadExt, AsyncWriteExt, TryFutureExt}; +use ipstack_geph::{IpStack, IpStackConfig}; +use smol::{ + channel::{Receiver, Sender}, + future::FutureExt as _, +}; + +const FAKE_LOCAL_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(100, 64, 89, 64)); + +pub struct VpnCapture { + ipstack: IpStack, +} + +impl VpnCapture { + pub fn new() -> Self { + let (send_captured, recv_captured) = smol::channel::unbounded(); + let (send_injected, recv_injected) = smol::channel::unbounded(); + smolscale::spawn( + packet_shuffle(send_captured, recv_injected) + .inspect_err(|e| tracing::error!(err = debug(e), "packet shuffle stopped")), + ) + .detach(); + let ipstack = IpStack::new(IpStackConfig::default(), recv_captured, send_injected); + Self { ipstack } + } + + pub fn ipstack(&self) -> &IpStack { + &self.ipstack + } +} + +async fn packet_shuffle( + send_captured: Sender, + recv_injected: Receiver, +) -> anyhow::Result<()> { + use std::os::fd::AsRawFd; + let tun_device = configure_tun_device(); + let fd_num = tun_device.as_raw_fd(); + let up_file = smol::Async::new(unsafe { std::fs::File::from_raw_fd(fd_num) }) + .context("cannot init up_file")?; + let (mut read, mut write) = up_file.split(); + let up = async { + loop { + let injected = recv_injected.recv().await?; + let _ = write.write(&injected).await?; + } + }; + let dn = async { + let mut buf = vec![0u8; 65536]; + loop { + let n = read.read(&mut buf).await?; + let buf = &buf[..n]; + tracing::trace!(n, "captured packet from TUN"); + send_captured.send(Bytes::copy_from_slice(buf)).await?; + } + }; + up.race(dn).await +} + +#[cfg(target_os = "linux")] +fn configure_tun_device() -> tun::platform::Device { + let device = tun::platform::Device::new( + tun::Configuration::default() + .name("tun-geph") + .address(FAKE_LOCAL_ADDR) + .netmask("255.255.255.0") + .destination("100.64.0.1") + .mtu(16384) + .up(), + ) + .expect("could not initialize TUN device"); + device +} diff --git a/binaries/geph5-client/src/vpn/linux_routing_setup.sh b/binaries/geph5-client/src/vpn/linux_routing_setup.sh new file mode 100644 index 0000000..199c8f6 --- /dev/null +++ b/binaries/geph5-client/src/vpn/linux_routing_setup.sh @@ -0,0 +1,18 @@ +export PATH=$PATH:/usr/sbin/:/sbin/ +ip route flush table 8964 +ip route add default dev tun-geph table 8964 + +ip rule del table main suppress_prefixlength 0 +ip rule add table main suppress_prefixlength 0 +ip rule del to all lookup 8964 pref 2 +ip rule add to all lookup 8964 pref 2 +iptables -t nat -D OUTPUT -p udp --dport 53 -j DNAT --to $GEPH_DNS +iptables -t nat -D OUTPUT -p tcp --dport 53 -j DNAT --to $GEPH_DNS +iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to $GEPH_DNS +iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to $GEPH_DNS + +# block ipv6 completely +ip6tables -D OUTPUT -o lo -j ACCEPT +ip6tables -A OUTPUT -o lo -j ACCEPT +ip6tables -D OUTPUT -j REJECT +ip6tables -A OUTPUT -j REJECT \ No newline at end of file