Kaynağa Gözat

fix: handle errors inside serve_tls

zu1k 3 yıl önce
ebeveyn
işleme
ba2db24a3a
3 değiştirilmiş dosya ile 97 ekleme ve 137 silme
  1. 34 22
      Cargo.lock
  2. 48 115
      crates/core/src/mitm.rs
  3. 15 0
      docs/guide/transparent_proxy.md

+ 34 - 22
Cargo.lock

@@ -58,16 +58,16 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7"
+checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7"
 dependencies = [
+ "async-lock",
  "autocfg",
  "concurrent-queue",
  "futures-lite",
  "libc",
  "log",
- "once_cell",
  "parking",
  "polling",
  "slab",
@@ -78,11 +78,12 @@ dependencies = [
 
 [[package]]
 name = "async-lock"
-version = "2.5.0"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
+checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
 dependencies = [
  "event-listener",
+ "futures-lite",
 ]
 
 [[package]]
@@ -736,9 +737,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.3.14"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be"
+checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
 dependencies = [
  "bytes",
  "fnv",
@@ -967,9 +968,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.135"
+version = "0.2.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
 
 [[package]]
 name = "libquickjs-sys"
@@ -1038,14 +1039,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
 [[package]]
 name = "mio"
-version = "0.8.4"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
+checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
 dependencies = [
  "libc",
  "log",
  "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.36.1",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1234,9 +1235,9 @@ dependencies = [
 
 [[package]]
 name = "os_str_bytes"
-version = "6.3.0"
+version = "6.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
+checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9"
 
 [[package]]
 name = "parking"
@@ -1290,15 +1291,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
 
 [[package]]
 name = "polling"
-version = "2.3.0"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011"
+checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2"
 dependencies = [
  "autocfg",
  "cfg-if",
@@ -1810,21 +1811,32 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.15"
+version = "0.3.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c"
+checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca"
 dependencies = [
  "itoa",
  "libc",
  "num_threads",
+ "serde",
+ "time-core",
  "time-macros",
 ]
 
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
 [[package]]
 name = "time-macros"
-version = "0.2.4"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
+checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b"
+dependencies = [
+ "time-core",
+]
 
 [[package]]
 name = "tokio"

+ 48 - 115
crates/core/src/mitm.rs

@@ -3,11 +3,7 @@ use crate::{
     handler::{CustomContextData, HttpHandler, MitmFilter},
     http_client::HttpClient,
 };
-use http::{
-    header,
-    uri::{PathAndQuery, Scheme},
-    HeaderValue, Uri,
-};
+use http::{header, uri::Scheme, HeaderValue, Uri};
 use hyper::{
     body::HttpBody, server::conn::Http, service::service_fn, upgrade::Upgraded, Body, Method,
     Request, Response,
@@ -60,7 +56,7 @@ where
         let res = if req.method() == Method::CONNECT {
             self.process_connect(req).await
         } else {
-            self.process_request(req).await
+            self.process_request(req, Scheme::HTTP).await
         };
 
         match res {
@@ -75,34 +71,39 @@ where
         }
     }
 
-    async fn process_request(self, mut req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
+    async fn process_request(
+        self,
+        mut req: Request<Body>,
+        scheme: Scheme,
+    ) -> Result<Response<Body>, hyper::Error> {
+        if req.uri().path().starts_with("/mitm/cert") {
+            return Ok(self.get_cert_res());
+        }
+
         let mut ctx = HttpContext {
             uri: None,
             should_modify_response: false,
             ..Default::default()
         };
 
-        let host = req
-            .headers()
-            .get(http::header::HOST)
-            .map(|h| h.to_str())
-            .unwrap()
-            .map(|h| h.to_owned())
-            .unwrap_or_default();
-
-        let uri = req.uri_mut();
-        if uri.authority().is_none() {
-            *uri = http::uri::Uri::builder()
-                .scheme(uri.scheme().unwrap_or(&Scheme::HTTP).as_str())
-                .authority(host.as_str())
-                .path_and_query(uri.path_and_query().map_or("/", |p| p.as_str()))
-                .build()
-                .unwrap();
-        }
+        // if req.uri().authority().is_none() {
+        if req.version() == http::Version::HTTP_10 || req.version() == http::Version::HTTP_11 {
+            let (mut parts, body) = req.into_parts();
+
+            if let Some(Ok(authority)) = parts
+                .headers
+                .get(http::header::HOST)
+                .map(|host| host.to_str())
+            {
+                let mut uri = parts.uri.into_parts();
+                uri.scheme = Some(scheme.clone());
+                uri.authority = authority.try_into().ok();
+                parts.uri = Uri::from_parts(uri).expect("build uri");
+            }
 
-        if req.uri().path().starts_with("/mitm/cert") || host.contains("cert.mitm") {
-            return Ok(self.get_cert_res());
-        }
+            req = Request::from_parts(parts, body);
+        };
+        // }
 
         let mut req = match self.http_handler.handle_request(&mut ctx, req).await {
             RequestOrResponse::Request(req) => req,
@@ -111,7 +112,7 @@ where
 
         {
             let header_mut = req.headers_mut();
-            // header_mut.remove(http::header::HOST);
+            header_mut.remove(http::header::HOST);
             header_mut.remove(http::header::ACCEPT_ENCODING);
             header_mut.remove(http::header::CONTENT_LENGTH);
         }
@@ -139,50 +140,6 @@ where
         Ok(res)
     }
 
-    // async fn process_connect(self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
-    //     let ctx = HttpContext {
-    //         uri: None,
-    //         should_modify_response: false,
-    //         ..Default::default()
-    //     };
-
-    //     if self.mitm_filter.filter(&ctx, &req).await {
-    //         tokio::task::spawn(async move {
-    //             let authority = req
-    //                 .uri()
-    //                 .authority()
-    //                 .expect("URI does not contain authority")
-    //                 .clone();
-
-    //             match hyper::upgrade::on(req).await {
-    //                 Ok(upgraded) => {
-    //                     let server_config = self.ca.clone().gen_server_config();
-
-    //                     let stream = TlsAcceptor::from(server_config)
-    //                         .accept(upgraded)
-    //                         .await
-    //                         .expect("Failed to establish TLS connection with client");
-
-    //                     if let Err(e) = self.serve_tls_stream(stream).await {
-    //                         let e_string = e.to_string();
-    //                         if !e_string.starts_with("error shutting down connection") {
-    //                             debug!("res:: {}", e);
-    //                         }
-    //                     }
-    //                 }
-    //                 Err(e) => debug!("upgrade error for {}: {}", authority, e),
-    //             };
-    //         });
-    //     } else {
-    //         tokio::task::spawn(async move {
-    //             let remote_addr = host_addr(req.uri()).unwrap();
-    //             let upgraded = hyper::upgrade::on(req).await.unwrap();
-    //             tunnel(upgraded, remote_addr).await
-    //         });
-    //     }
-    //     Ok(Response::new(Body::empty()))
-    // }
-
     async fn process_connect(self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
         let ctx = HttpContext {
             uri: None,
@@ -218,54 +175,30 @@ where
     pub async fn serve_tls<IO: AsyncRead + AsyncWrite + Unpin + Send + 'static>(self, stream: IO) {
         let server_config = self.ca.clone().gen_server_config();
 
-        let stream = TlsAcceptor::from(server_config)
-            .accept(stream)
-            .await
-            .expect("Failed to establish TLS connection with client");
-
-        if let Err(e) = self.serve_tls_stream(stream).await {
-            let e_string = e.to_string();
-            if !e_string.starts_with("error shutting down connection") {
-                debug!("res:: {}", e);
+        match TlsAcceptor::from(server_config).accept(stream).await {
+            Ok(stream) => {
+                if let Err(e) = self.serve_stream(stream, Scheme::HTTPS).await {
+                    let e_string = e.to_string();
+                    if !e_string.starts_with("error shutting down connection") {
+                        debug!("res:: {}", e);
+                    }
+                }
+            }
+            Err(err) => {
+                error!("Tls accept failed: {err}")
             }
         }
     }
 
-    async fn serve_tls_stream<IO: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
-        self,
-        stream: tokio_rustls::server::TlsStream<IO>,
-    ) -> Result<(), hyper::Error> {
-        let service = service_fn(|mut req| {
-            if req.version() == http::Version::HTTP_10 || req.version() == http::Version::HTTP_11 {
-                let authority = req
-                    .headers()
-                    .get(http::header::HOST)
-                    .expect("Host is a required header")
-                    .to_str()
-                    .expect("Failed to convert host to str");
-
-                let uri = http::uri::Builder::new()
-                    .scheme(http::uri::Scheme::HTTPS)
-                    .authority(authority)
-                    .path_and_query(
-                        req.uri()
-                            .path_and_query()
-                            .unwrap_or(&PathAndQuery::from_static("/"))
-                            .to_owned(),
-                    )
-                    .build()
-                    .expect("Failed to build URI");
-
-                let (mut parts, body) = req.into_parts();
-                parts.uri = uri;
-                req = Request::from_parts(parts, body)
-            };
-
-            self.clone().process_request(req)
-        });
-
+    async fn serve_stream<S>(self, stream: S, scheme: Scheme) -> Result<(), hyper::Error>
+    where
+        S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
+    {
         Http::new()
-            .serve_connection(stream, service)
+            .serve_connection(
+                stream,
+                service_fn(|req| self.clone().process_request(req, scheme.clone())),
+            )
             .with_upgrades()
             .await
     }

+ 15 - 0
docs/guide/transparent_proxy.md

@@ -0,0 +1,15 @@
+# 透明代理
+
+See https://docs.mitmproxy.org/stable/howto-transparent/ for docs.
+
+```shell
+sudo sysctl -w net.ipv4.ip_forward=1
+sudo sysctl -w net.ipv6.conf.all.forwarding=1
+sudo sysctl -w net.ipv4.conf.all.send_redirects=0
+
+sudo useradd --create-home mitm
+sudo -u mitm -H bash -c 'good-mitm run -r rules/log.yaml -b 0.0.0.0:8080'
+
+iptables -t nat -A OUTPUT -p tcp -m owner ! --uid-owner mitm --dport 80 -j REDIRECT --to-port 8080
+iptables -t nat -A OUTPUT -p tcp -m owner ! --uid-owner mitm --dport 443 -j REDIRECT --to-port 8081
+```