use crate::mut_qstring::MutQString; use crate::tun::{BorrowedTunIo, RewindStream}; use crate::{error, info, warn}; use bytes::Bytes; use http::Uri; use http_body_util::{BodyExt, Full}; use hyper::body::Incoming; use hyper_util::rt::TokioIo; use ipstack::IpStackStream; use regex::Regex; use rustls::RootCertStore; use std::io; use std::net::SocketAddr; use std::os::fd::RawFd; use std::sync::{Arc, Mutex as StdMutex, OnceLock}; use std::time::Duration; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::UdpSocket; use tokio::task::JoinSet; use tokio_rustls::StartHandshake; use tokio_util::sync::CancellationToken; use tun_rs::BorrowedAsyncDevice; use crate::acceptor::build_tls_acceptor; static ROOT_STORE: OnceLock> = OnceLock::new(); const BLOCKED_HOSTS: &[&str] = &["submit.backtrace.io"]; fn is_blocked_host(host: &str) -> bool { let host = host.trim_end_matches('.'); BLOCKED_HOSTS .iter() .any(|blocked| host.eq_ignore_ascii_case(blocked)) } const SNIFF_TIMEOUT: Duration = Duration::from_secs(5); const TEARDOWN_DRAIN_TIMEOUT: Duration = Duration::from_secs(3); const TEARDOWN_SETTLE: Duration = Duration::from_millis(50); const TEARDOWN_FIN_TIMEOUT: Duration = Duration::from_millis(300); const TEARDOWN_GRACE: Duration = Duration::from_secs(4); fn root_store() -> Arc { ROOT_STORE .get_or_init(|| { let mut root_store = RootCertStore::empty(); let native_certs = rustls_native_certs::load_native_certs(); for err in native_certs.errors { warn!("加载证书警告: {err}"); } for cert in native_certs.certs { let _ = root_store.add(cert); } load_system_ca_dirs(&mut root_store); if root_store.is_empty() { root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); } Arc::new(root_store) }) .clone() } fn load_system_ca_dirs(store: &mut RootCertStore) { use std::io::BufReader; const DIRS: &[&str] = &[ "/system/etc/security/cacerts", // Android 13- "/apex/com.android.conscrypt/cacerts", // Android 14+ ]; for dir in DIRS { let Ok(entries) = std::fs::read_dir(dir) else { continue; }; for entry in entries.flatten() { let Ok(file) = std::fs::File::open(entry.path()) else { continue; }; for cert in rustls_pemfile::certs(&mut BufReader::new(file)).flatten() { let _ = store.add(cert); } } } } async fn sniff_client_hello( io: &mut RewindStream, ) -> io::Result, rustls::server::Accepted)>> where S: AsyncRead + AsyncWrite + Unpin, { let mut acceptor = rustls::server::Acceptor::default(); let mut buf = vec![0u8; 16 * 1024]; loop { let n = match tokio::time::timeout(SNIFF_TIMEOUT, io.read(&mut buf)).await { Ok(Ok(n)) => n, Ok(Err(e)) => return Err(e), Err(_) => return Ok(None), // 对端迟迟不发数据,当作非 TLS }; if n == 0 { return Ok(None); } if acceptor.read_tls(&mut &buf[..n]).is_err() { return Ok(None); } match acceptor.accept() { Ok(Some(accepted)) => { let sni = accepted.client_hello().server_name().map(|s| s.to_string()); return Ok(Some((sni, accepted))); } Ok(None) => continue, // 还没凑齐 Err(_) => return Ok(None), // 不是合法 TLS } } } #[allow(clippy::too_many_arguments)] async fn handle_tls_connection( tcp: S, dst: SocketAddr, upstream: Upstream, target_hosts: Vec, waiters: Waiters, server_config: Arc, cancel: CancellationToken, ) -> anyhow::Result<()> where S: AsyncRead + AsyncWrite + Unpin + Send + 'static, { let mut io = RewindStream::new(tcp); let sniffed = match sniff_client_hello(&mut io).await { Ok(v) => v, Err(e) => { warn!("client hello sniff error on {dst:?}: {e:?}"); return Ok(()); } }; let Some((sni, accepted)) = sniffed else { io.rewind(); io.stop_recording(); return passthrough_cancel_wrapped(io, dst, None, upstream, &cancel).await; }; if let Some(host) = sni.as_deref() && is_blocked_host(host) { warn!("blocked host {host} ({dst:?}), connection rejected"); return Ok(()); } let host_matched = sni .as_deref() .map(|s| target_hosts.iter().any(|h| s.eq_ignore_ascii_case(h))) .unwrap_or(false); if !host_matched { io.rewind(); io.stop_recording(); let r = passthrough_cancel_wrapped(io, dst, sni.as_deref(), upstream, &cancel).await; if r.is_err() { warn!("tcp_passthrough error on {sni:?} {dst:?}"); } return r; } io.stop_recording(); let client_tls = StartHandshake::from_parts(accepted, io) .into_stream(server_config) .await?; let svc_host = sni.clone().unwrap_or_default(); let svc = hyper::service::service_fn(move |req: hyper::Request| { let host = svc_host.clone(); let waiters = waiters.clone(); let upstream = upstream.clone(); async move { Ok::<_, std::convert::Infallible>( forward_request(req, dst, host, waiters, upstream).await, ) } }); let conn = hyper::server::conn::http1::Builder::new().serve_connection(TokioIo::new(client_tls), svc); tokio::pin!(conn); let mut graceful = false; loop { tokio::select! { res = conn.as_mut() => { match res { Ok(()) => {} Err(e) => warn!("downstream http1 connection error: {e:?}"), } break; } _ = cancel.cancelled(), if !graceful => { info!("teardown: graceful shutdown of downstream connection"); conn.as_mut().graceful_shutdown(); graceful = true; } } } Ok(()) } async fn passthrough_cancel_wrapped( io: IO, dst: SocketAddr, sni: Option<&str>, upstream: Upstream, cancel: &CancellationToken, ) -> anyhow::Result<()> where IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, { tokio::select! { _ = cancel.cancelled() => Ok(()), r = tcp_passthrough(io, dst, sni, upstream) => r, } } #[allow(clippy::too_many_arguments)] async fn forward_request( mut req: hyper::Request, dst: SocketAddr, url_host: String, waiters: Waiters, upstream: Upstream, ) -> hyper::Response> { if let Some(rewritten) = force_full_mysekai(req.uri()) { warn!("rewrite mysekai flag: {} -> {}", req.uri(), rewritten); *req.uri_mut() = rewritten; } let uri = req.uri().clone(); let url = build_url(&url_host, &uri); let path_and_query = uri .path_and_query() .map(|p| p.as_str().to_string()) .unwrap_or_else(|| "/".to_string()); let waiter = take_matching_waiter(&waiters, &path_and_query); if let Some(w) = &waiter { info!("step '{}' matched", w.id); } let want_body = waiter.is_some(); match forward_request_inner(req, dst, &url_host, upstream, want_body).await { Ok((response, saved_body)) => { if let (Some(w), Some(body)) = (waiter, saved_body) { save_and_notify(w, url, body); } response } Err(e) => { error!("forward to {dst} failed: {e:?}"); if let Some(w) = waiter { crate::notify_step_result(&w.id, None); } hyper::Response::builder() .status(hyper::StatusCode::BAD_GATEWAY) .header("content-type", "text/plain; charset=utf-8") .body(Full::new(Bytes::from(format!("mitm forward error: {e}")))) .unwrap() } } } fn save_and_notify(waiter: Waiter, url: String, body: Bytes) { tokio::spawn(async move { let path = std::path::Path::new(&waiter.save_path); if !path.is_absolute() { error!("save_path 不是绝对路径({})", waiter.save_path); } if let Some(parent) = path.parent() { let _ = tokio::fs::create_dir_all(parent).await; } match tokio::fs::write(path, &body).await { Ok(()) => { info!("step '{}' saved ({} bytes)", waiter.id, body.len()); crate::notify_step_result(&waiter.id, Some(&url)); } Err(e) => { error!("write {} failed: {e:?}", waiter.save_path); crate::notify_step_result(&waiter.id, None); } } }); } async fn forward_request_inner( req: hyper::Request, dst: SocketAddr, url_host: &str, upstream: Upstream, want_body: bool, ) -> anyhow::Result<(hyper::Response>, Option)> { let (mut parts, body) = req.into_parts(); let body_bytes = body.collect().await?.to_bytes(); // 拒绝长链接 for h in [ "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "te", "trailer", "content-length", ] { parts.headers.remove(h); } let mut client_config = rustls::ClientConfig::builder() .with_root_certificates(root_store()) .with_no_client_auth(); client_config.alpn_protocols = vec![b"http/1.1".to_vec()]; let connector = tokio_rustls::TlsConnector::from(Arc::new(client_config)); let server_name = rustls::pki_types::ServerName::try_from(url_host.to_string())?; let tcp_to_server = upstream.connect(dst, Some(url_host)).await?; let server_tls = connector.connect(server_name, tcp_to_server).await?; let (mut sender, conn) = hyper::client::conn::http1::handshake::<_, Full>(TokioIo::new(server_tls)).await?; tokio::spawn(async move { if let Err(e) = conn.await { warn!("upstream http1 connection error: {e:?}"); } }); let upstream_req = hyper::Request::from_parts(parts, Full::new(body_bytes)); let resp = sender.send_request(upstream_req).await?; let (mut res_parts, res_body) = resp.into_parts(); let res_bytes = res_body.collect().await?.to_bytes(); for h in [ "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "te", "trailer", ] { res_parts.headers.remove(h); } let saved = if want_body { Some(res_bytes.clone()) } else { None }; Ok(( hyper::Response::from_parts(res_parts, Full::new(res_bytes)), saved, )) } fn build_url(host: &str, uri: &Uri) -> String { if uri.scheme().is_some() && uri.authority().is_some() { return uri.to_string(); } let pq = uri.path_and_query().map(|p| p.as_str()).unwrap_or("/"); format!("https://{host}{pq}") } fn force_full_mysekai(uri: &Uri) -> Option { const KEY: &str = "isForceAllReloadOnlyMysekai"; let mut query = qstring::QString::from(uri.query()?); if query.get(KEY) != Some("False") { return None; } query.set(KEY, "True"); Uri::try_from(format!("{}?{}", uri.path(), query).as_str()).ok() } #[derive(serde::Deserialize, Debug, Clone)] pub struct StepSpec { pub id: String, #[serde(default)] pub hosts: Vec, pub path: String, #[serde(rename = "savePath")] pub save_path: String, /// 持续运行:命中后不摘除 waiter,同类型的包可以反复抓 #[serde(default)] pub repeat: bool, } #[derive(Clone)] pub struct Waiter { pub id: String, pub path: Regex, pub save_path: String, pub repeat: bool, } pub type Waiters = Arc>>; fn take_matching_waiter(waiters: &StdMutex>, path_and_query: &str) -> Option { let mut list = waiters.lock().unwrap(); let idx = list.iter().position(|w| w.path.is_match(path_and_query))?; if list[idx].repeat { // 持续运行:保留 waiter,让后续同类型的请求继续命中 Some(list[idx].clone()) } else { Some(list.remove(idx)) } } #[derive(Clone, Debug)] pub struct ProxyConfig { pub tls: bool, pub host: String, pub port: u16, pub auth: Option, } impl ProxyConfig { pub fn parse(input: &str) -> anyhow::Result> { let raw = input.trim(); if raw.is_empty() { return Ok(None); } let url = url::Url::parse(raw) .map_err(|e| anyhow::anyhow!("代理地址不是合法 URL: {raw} ({e})"))?; let tls = match url.scheme() { "http" => false, "https" => true, other => anyhow::bail!("代理只支持 http/https,当前是 {other}://"), }; let host = url .host_str() .filter(|h| !h.is_empty()) .ok_or_else(|| anyhow::anyhow!("代理地址缺少 host: {raw}"))? .to_string(); let port = url .port_or_known_default() .ok_or_else(|| anyhow::anyhow!("代理地址缺少端口: {raw}"))?; let auth = if url.username().is_empty() { None } else { let pass = url.password().unwrap_or(""); Some(base64_encode( format!("{}:{}", url.username(), pass).as_bytes(), )) }; Ok(Some(Self { tls, host, port, auth, })) } } fn base64_encode(data: &[u8]) -> String { use base64::Engine as _; base64::engine::general_purpose::STANDARD.encode(data) } type UpstreamIo = Box; trait AsyncReadWrite: AsyncRead + AsyncWrite {} impl AsyncReadWrite for T {} #[derive(Clone)] struct Upstream { proxy: Option>, } impl Upstream { fn new(proxy: Option) -> Self { Self { proxy: proxy.map(Arc::new), } } async fn connect(&self, dst: SocketAddr, host: Option<&str>) -> anyhow::Result { match &self.proxy { None => { let tcp = tokio::net::TcpStream::connect(dst).await?; tcp.set_nodelay(true).ok(); Ok(Box::new(tcp)) } Some(proxy) => connect_via_proxy(proxy, dst, host).await, } } } async fn connect_via_proxy( proxy: &ProxyConfig, dst: SocketAddr, host: Option<&str>, ) -> anyhow::Result { let tcp = tokio::net::TcpStream::connect((proxy.host.as_str(), proxy.port)) .await .map_err(|e| anyhow::anyhow!("连接代理 {}:{} 失败: {e}", proxy.host, proxy.port))?; tcp.set_nodelay(true).ok(); let mut io: UpstreamIo = if proxy.tls { let mut cfg = rustls::ClientConfig::builder() .with_root_certificates(root_store()) .with_no_client_auth(); cfg.alpn_protocols = vec![b"http/1.1".to_vec()]; let connector = tokio_rustls::TlsConnector::from(Arc::new(cfg)); let name = rustls::pki_types::ServerName::try_from(proxy.host.clone())?; Box::new( connector .connect(name, tcp) .await .map_err(|e| anyhow::anyhow!("与代理建立 TLS 失败: {e}"))?, ) } else { Box::new(tcp) }; let authority = match host { Some(h) if !h.is_empty() => format!("{h}:{}", dst.port()), _ => dst.to_string(), }; let mut req = format!("CONNECT {authority} HTTP/1.1\r\nHost: {authority}\r\n"); if let Some(auth) = &proxy.auth { req.push_str(&format!("Proxy-Authorization: Basic {auth}\r\n")); } req.push_str("\r\n"); io.write_all(req.as_bytes()).await?; io.flush().await?; let mut head = Vec::with_capacity(256); let mut byte = [0u8; 1]; loop { let n = io.read(&mut byte).await?; if n == 0 { anyhow::bail!("代理在 CONNECT 阶段直接断开"); } head.push(byte[0]); if head.ends_with(b"\r\n\r\n") { break; } if head.len() > 16 * 1024 { anyhow::bail!("代理 CONNECT 响应头过长"); } } let head_str = String::from_utf8_lossy(&head); let status_line = head_str.lines().next().unwrap_or("").trim().to_string(); let ok = status_line .split_whitespace() .nth(1) .map(|code| code == "200") .unwrap_or(false); if !ok { anyhow::bail!("代理 CONNECT 失败: {status_line}"); } Ok(io) } pub async fn run_session( tun_fd: i32, target_hosts: Vec, proxy: Option, waiters: Waiters, cancel: CancellationToken, ) -> anyhow::Result<()> { let device = unsafe { BorrowedAsyncDevice::borrow_raw(tun_fd as RawFd)? }; let mut ipstack_config = ipstack::IpStackConfig::default(); ipstack_config.mtu(1500)?; let device_ref: &BorrowedAsyncDevice<'static> = unsafe { std::mem::transmute(&device) }; let tun_io = BorrowedTunIo::new(device_ref); let mut ip_stack = ipstack::IpStack::new(ipstack_config, tun_io); let mut bridge_tasks = JoinSet::new(); let tls_acceptor = build_tls_acceptor(target_hosts.clone()); let upstream = Upstream::new(proxy); loop { let token = cancel.clone(); tokio::select! { _ = cancel.cancelled() => { break; } _ = bridge_tasks.join_next(), if !bridge_tasks.is_empty() => {} res = ip_stack.accept() => { let stream = match res { Ok(s) => s, Err(e) => { error!("ip_stack accept error (device might be dropped): {e:?}"); break; } }; match stream { IpStackStream::Udp(udp) => { let dst = udp.peer_addr(); if dst.port() == 443 { // drop quic roughly drop(udp); } else { bridge_tasks.spawn(async move { if let Err(e) = udp_passthrough(udp, dst, token).await { error!("udp passthrough error: {e:?}"); } }); } } IpStackStream::Tcp(mut tcp) => { let dst = tcp.peer_addr(); if dst.port() == 443 { let (app_io, mut tun_io) = tokio::io::duplex(256 * 1024); let pump_token = cancel.clone(); bridge_tasks.spawn(async move { tokio::select! { _ = pump_token.cancelled() => { let _ = tokio::time::timeout( TEARDOWN_DRAIN_TIMEOUT, tokio::io::copy(&mut tun_io, &mut tcp), ) .await; } _ = tokio::io::copy_bidirectional(&mut tun_io, &mut tcp) => {} } tokio::time::sleep(TEARDOWN_SETTLE).await; let _ = tokio::time::timeout( TEARDOWN_FIN_TIMEOUT, tcp.shutdown(), ) .await; }); let hosts = target_hosts.clone(); let server_config = tls_acceptor.clone(); let token = cancel.clone(); let waiters = waiters.clone(); let upstream = upstream.clone(); tokio::spawn(async move { match handle_tls_connection( app_io, dst, upstream, hosts, waiters, server_config, token, ).await { Ok(()) => {} Err(e) => error!("handle tls conn error: {e:?}"), } }); } else { let pump_token = cancel.clone(); let upstream = upstream.clone(); bridge_tasks.spawn(async move { let _ = tcp_passthrough_cancellable(tcp, dst, None, upstream, pump_token) .await; }); } } _ => {} } } } } let _ = tokio::time::timeout(TEARDOWN_GRACE, async { while bridge_tasks.join_next().await.is_some() {} }) .await; bridge_tasks.abort_all(); while bridge_tasks.join_next().await.is_some() {} info!("session stopped"); Ok(()) } async fn tcp_passthrough( mut client: IO, dst: SocketAddr, sni: Option<&str>, upstream: Upstream, ) -> anyhow::Result<()> where IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, { let mut server = upstream.connect(dst, sni).await?; tokio::io::copy_bidirectional(&mut client, &mut server).await?; Ok(()) } async fn tcp_passthrough_cancellable( mut client: IO, dst: SocketAddr, sni: Option<&str>, upstream: Upstream, cancel: CancellationToken, ) -> anyhow::Result<()> where IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, { let mut server = upstream.connect(dst, sni).await?; tokio::select! { _ = cancel.cancelled() => { let _ = tokio::time::timeout( TEARDOWN_DRAIN_TIMEOUT, tokio::io::copy(&mut client, &mut server), ) .await; } res = tokio::io::copy_bidirectional(&mut client, &mut server) => { res?; } } tokio::time::sleep(TEARDOWN_SETTLE).await; let _ = tokio::time::timeout(TEARDOWN_FIN_TIMEOUT, client.shutdown()).await; let _ = tokio::time::timeout(TEARDOWN_FIN_TIMEOUT, server.shutdown()).await; Ok(()) } async fn udp_passthrough( client: IO, dst: SocketAddr, cancel: CancellationToken, ) -> anyhow::Result<()> where IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, { let bind_addr: SocketAddr = if dst.is_ipv4() { "0.0.0.0:0".parse()? } else { "[::]:0".parse()? }; let socket = UdpSocket::bind(bind_addr).await?; socket.connect(dst).await?; let socket = Arc::new(socket); let (mut cr, mut cw) = tokio::io::split(client); let mut client_buf = vec![0u8; 65535]; let mut server_buf = vec![0u8; 65535]; let idle_timeout = Duration::from_secs(12); loop { tokio::select! { _ = cancel.cancelled() => break, // c2s res = tokio::time::timeout(idle_timeout, cr.read(&mut client_buf)) => { match res { Ok(Ok(0)) | Err(_) => break, Ok(Ok(n)) => { if socket.send(&client_buf[..n]).await.is_err() { break; } } Ok(Err(_)) => break, } } // s2c res = tokio::time::timeout(idle_timeout, socket.recv(&mut server_buf)) => { match res { Ok(Ok(n)) if n > 0 => { if cw.write_all(&server_buf[..n]).await.is_err() { break; } } _ => break, } } } } Ok(()) }