use std::{path::PathBuf, str::FromStr, sync::LazyLock};
use crate::fronts::parse_fronts;
use anyhow::Context;
use geph4_protocol::binder::protocol::{BinderClient, Credentials};
use geph5_client::{BridgeMode, BrokerSource, Config};
use serde::{Deserialize, Serialize};
use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqliteSynchronous},
SqlitePool,
};
use std::net::{Ipv4Addr, SocketAddr};
use stdcode::StdcodeSerializeExt;
use structopt::StructOpt;
use tmelcrypt::Ed25519SK;
#[derive(Debug, StructOpt, Deserialize, Serialize, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum Opt {
Connect(ConnectOpt),
BridgeTest(crate::main_bridgetest::BridgeTestOpt),
Sync(crate::sync::SyncOpt),
BinderProxy(crate::binderproxy::BinderProxyOpt),
DebugPack(crate::debugpack::DebugPackOpt),
}
#[derive(Debug, StructOpt, Clone, Deserialize, Serialize)]
pub struct ConnectOpt {
#[structopt(flatten)]
pub common: CommonOpt,
#[structopt(flatten)]
pub auth: AuthOpt,
#[structopt(long)]
pub use_bridges: bool,
#[structopt(long)]
pub override_connect: Option<String>,
#[structopt(long)]
pub force_bridge: Option<Ipv4Addr>,
#[structopt(long, default_value = "1")]
pub udp_shard_count: usize,
#[structopt(long, default_value = "30")]
pub udp_shard_lifetime: u64,
#[structopt(long, default_value = "2")]
pub tcp_shard_count: usize,
#[structopt(long, default_value = "10")]
pub tcp_shard_lifetime: u64,
#[structopt(long, default_value = "127.0.0.1:9910")]
pub http_listen: SocketAddr,
#[structopt(long, default_value = "127.0.0.1:9909")]
pub socks5_listen: SocketAddr,
#[structopt(long, default_value = "127.0.0.1:9809")]
pub stats_listen: SocketAddr,
#[structopt(long, default_value = "127.0.0.1:15353")]
pub dns_listen: SocketAddr,
#[structopt(long)]
pub exit_server: Option<String>,
#[structopt(long)]
pub exclude_prc: bool,
#[structopt(long)]
pub stdio_vpn: bool,
#[structopt(long)]
pub sticky_bridges: bool,
#[structopt(long)]
pub vpn_mode: Option<VpnMode>,
#[structopt(long)]
pub force_protocol: Option<String>,
#[structopt(long)]
pub forward_ports: Vec<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Serialize, Deserialize)]
pub enum VpnMode {
#[cfg(any(target_os = "linux", target_os = "android"))]
InheritedFd,
#[cfg(target_os = "linux")]
TunNoRoute,
#[cfg(target_os = "linux")]
TunRoute,
#[cfg(windows)]
WinDivert,
Stdio,
}
impl FromStr for VpnMode {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#[cfg(any(target_os = "linux", target_os = "android"))]
"inherited-fd" => Ok(Self::InheritedFd),
#[cfg(target_os = "linux")]
"tun-no-route" => Ok(Self::TunNoRoute),
#[cfg(target_os = "linux")]
"tun-route" => Ok(Self::TunRoute),
#[cfg(windows)]
"windivert" => Ok(Self::WinDivert),
"stdio" => Ok(Self::Stdio),
x => anyhow::bail!("unrecognized VPN mode {}", x),
}
}
}
#[derive(Debug, StructOpt, Clone, Deserialize, Serialize)]
pub struct CommonOpt {
#[structopt(
long,
default_value = "https://www.netlify.com/v4/next-gen,https://vuejs.org/v4/next-gen,https://www.cdn77.com/next-gen,https://dtnins2n354c4.cloudfront.net/v4/next-gen"
)]
binder_http_fronts: String,
#[structopt(
long,
default_value = "svitania-naidallszei.netlify.app,svitania-naidallszei.netlify.app,1049933718.rsc.cdn77.org,dtnins2n354c4.cloudfront.net"
)]
binder_http_hosts: String,
#[structopt(
long,
default_value = "124526f4e692b589511369687498cce57492bf4da20f8d26019c1cc0c80b6e4b",
parse(from_str = str_to_x25519_pk)
)]
binder_master: x25519_dalek::PublicKey,
#[structopt(
long,
default_value = "4e01116de3721cc702f4c260977f4a1809194e9d3df803e17bb90db2a425e5ee",
parse(from_str = str_to_mizaru_pk)
)]
binder_mizaru_free: mizaru::PublicKey,
#[structopt(
long,
default_value = "44ab86f527fbfb5a038cc51a49e0467be6eb532c4b9c6cb5cdb430926c95bdab",
parse(from_str = str_to_mizaru_pk)
)]
binder_mizaru_plus: mizaru::PublicKey,
#[structopt(long, default_value = "file::memory:?cache=shared")]
pub debugpack_path: String,
}
impl CommonOpt {
pub fn get_binder_client(&self) -> BinderClient {
BinderClient(parse_fronts(
*self.binder_master.as_bytes(),
self.binder_http_fronts
.split(',')
.zip(self.binder_http_hosts.split(','))
.map(|(k, v)| (k.to_string(), v.to_string())),
))
}
}
#[derive(Debug, StructOpt, Clone, Deserialize, Serialize)]
pub struct AuthOpt {
#[structopt(
long,
default_value = "auto",
parse(from_str = str_to_path)
)]
pub credential_cache: PathBuf,
#[structopt(subcommand)]
pub auth_kind: Option<AuthKind>,
}
#[derive(Debug, StructOpt, Clone, Deserialize, Serialize)]
#[structopt(name = "auth_kind")]
pub enum AuthKind {
AuthPassword {
#[structopt(long, default_value = "")]
username: String,
#[structopt(long, default_value = "")]
password: String,
},
AuthKeypair {
#[structopt(long, default_value = "")]
sk_path: String,
},
}
fn str_to_path(src: &str) -> PathBuf {
if src == "auto" {
let mut config_dir = dirs::config_dir().unwrap();
config_dir.push("geph4-credentials");
config_dir
} else {
PathBuf::from(src)
}
}
fn str_to_x25519_pk(src: &str) -> x25519_dalek::PublicKey {
let raw_bts = hex::decode(src).unwrap();
let raw_bts: [u8; 32] = raw_bts.as_slice().try_into().unwrap();
x25519_dalek::PublicKey::from(raw_bts)
}
fn str_to_mizaru_pk(src: &str) -> mizaru::PublicKey {
let raw_bts = hex::decode(src).unwrap();
let raw_bts: [u8; 32] = raw_bts.as_slice().try_into().unwrap();
mizaru::PublicKey(raw_bts)
}
pub static GEPH5_CONFIG_TEMPLATE: LazyLock<Config> = LazyLock::new(|| Config {
socks5_listen: None,
http_proxy_listen: None,
control_listen: None,
exit_constraint: geph5_client::ExitConstraint::Auto,
bridge_mode: BridgeMode::Auto,
cache: None,
broker: Some(BrokerSource::Race(vec![
BrokerSource::Fronted {
front: "https://vuejs.org".into(),
host: "svitania-naidallszei-2.netlify.app".into(),
},
BrokerSource::AwsLambda {
function_name: "geph-lambda-bouncer".into(),
region: "us-east-1".into(),
access_key_id: String::from_utf8_lossy(
&base32::decode(
base32::Alphabet::Crockford,
"855MJGAMB58MCPJBB97K4P2C6NC36DT8",
)
.unwrap(),
)
.to_string(),
secret_access_key: String::from_utf8_lossy(
&base32::decode(
base32::Alphabet::Crockford,
"8SQ7ECABES132WT4B9GQEN356XQ6GRT36NS64GBK9HP42EAGD8W6JRA39DTKAP2J",
)
.unwrap(),
)
.to_string(),
},
])),
vpn: false,
spoof_dns: true,
passthrough_china: false,
dry_run: false,
credentials: geph5_broker_protocol::Credential::TestDummy,
});