Skip to content

Commit

Permalink
Merge branch 'master' of github.com:geph-official/geph5
Browse files Browse the repository at this point in the history
  • Loading branch information
nullchinchilla committed Jun 26, 2024
2 parents 9ae5743 + abdf0e1 commit 0da9120
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 93 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions binaries/geph5-client-gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,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);
}
}

Expand Down
1 change: 1 addition & 0 deletions binaries/geph5-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
95 changes: 5 additions & 90 deletions binaries/geph5-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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,
Expand Down Expand Up @@ -138,94 +140,7 @@ async fn client_main(ctx: AnyCtx<Config>) -> 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::<anyhow::Result<()>>(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)),
Expand Down
18 changes: 17 additions & 1 deletion binaries/geph5-client/src/client_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Config>, dest_addr: &str) -> anyhow::Result<picomux::Stream> {
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;
Expand Down
173 changes: 173 additions & 0 deletions binaries/geph5-client/src/vpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, PacketFlag, QTYPE};
use smol::{
future::FutureExt,
io::{BufReader, BufWriter},
};
#[cfg(target_os = "windows")]
pub use windows::*;

Expand All @@ -16,3 +32,160 @@ mod macos;

#[cfg(target_os = "macos")]
pub use macos::*;

use crate::{client::CtxField, client_inner::open_conn, Config};

static FAKE_DNS_FORWARD: CtxField<Cache<String, Ipv4Addr>> = |_| {
Cache::builder()
.time_to_live(Duration::from_secs(300))
.build()
};

static FAKE_DNS_BACKWARD: CtxField<Cache<Ipv4Addr, String>> = |_| {
Cache::builder()
.time_to_live(Duration::from_secs(600))
.build()
};

pub fn fake_dns_backtranslate(ctx: &AnyCtx<Config>, fake: Ipv4Addr) -> Option<String> {
tracing::trace!(fake = debug(fake), "attempting to backtranslate");
ctx.get(FAKE_DNS_BACKWARD).get(&fake)
}

pub fn fake_dns_allocate(ctx: &AnyCtx<Config>, 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<Config>) -> 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::<anyhow::Result<()>>(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::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) {
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 = pkt.into_reply();
response.answers = answers;

captured
.send(&response.build_bytes_vec_compressed()?)
.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
}
}

0 comments on commit 0da9120

Please sign in to comment.