Skip to content

Commit 0d40d76

Browse files
committed
http: separate the transparent and explicit proxy
Previously we used the same "interception" of outgoing TCP flows for both transparent and explicit proxies. We should actually use interception only for the transparent mode, and use a regular local port bind for the explicit proxy. Signed-off-by: David Scott <[email protected]>
1 parent 84defc5 commit 0d40d76

File tree

4 files changed

+107
-44
lines changed

4 files changed

+107
-44
lines changed

Diff for: src/hostnet/hostnet_http.ml

+5-1
Original file line numberDiff line numberDiff line change
@@ -601,13 +601,17 @@ module Make
601601
in
602602
Lwt.return listeners
603603

604-
let handle ~dst:(ip, port) ~t =
604+
let transparent_proxy_handler ~dst:(ip, port) ~t =
605605
match port, t.http, t.https with
606606
| 80, Some h, _ -> Some (http ~dst:ip ~t h)
607607
| 443, _, Some h ->
608608
if Exclude.matches ip None t.exclude
609609
then None
610610
else Some (https ~dst:ip h)
611+
| _, _, _ -> None
612+
613+
let explicit_proxy_handler ~dst:(ip, port) ~t =
614+
match port, t.http, t.https with
611615
| 3128, Some h, _ -> Some (tcp ~dst:(ip, port) h)
612616
| 3128, None, _ -> Some (proxy ())
613617
| _, _, _ -> None

Diff for: src/hostnet/hostnet_http.mli

+9-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ sig
3434
val to_json: t -> Ezjsonm.t
3535
(** [to_json t] encodes [t] into json *)
3636

37-
val handle: dst:(Ipaddr.V4.t * int) -> t:t ->
37+
val transparent_proxy_handler: dst:(Ipaddr.V4.t * int) -> t:t ->
3838
(int -> (Tcp.flow -> unit Lwt.t) option) Lwt.t option
39+
(** Intercept outgoing HTTP flows and redirect to the upstream proxy
40+
if one is defined. *)
41+
42+
val explicit_proxy_handler: dst:(Ipaddr.V4.t * int) -> t:t ->
43+
(int -> (Tcp.flow -> unit Lwt.t) option) Lwt.t option
44+
(** Intercept outgoing HTTP proxy flows and if an upstream proxy is
45+
defined, redirect to it, otherwise implement the proxy function
46+
ourselves. *)
3947

4048
end

Diff for: src/hostnet/slirp.ml

+15-4
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,20 @@ struct
536536
let id =
537537
Stack_tcp_wire.v ~src_port:dst_port ~dst:src ~src:dst ~dst_port:src_port
538538
in
539-
Endpoint.input_tcp t.endpoint ~id ~syn
540-
(Ipaddr.V4 Ipaddr.V4.localhost, dst_port) raw
541-
>|= ok
539+
(* local HTTP proxy *)
540+
let callback = match !http with
541+
| None -> None
542+
| Some http -> Http_forwarder.explicit_proxy_handler ~dst:(dst, dst_port) ~t:http
543+
in
544+
begin match callback with
545+
| None ->
546+
Endpoint.input_tcp t.endpoint ~id ~syn
547+
(Ipaddr.V4 Ipaddr.V4.localhost, dst_port) raw
548+
>|= ok
549+
| Some cb ->
550+
Endpoint.intercept_tcp_syn t.endpoint ~id ~syn (fun _ -> cb) raw
551+
>|= ok
552+
end
542553
| _ ->
543554
Lwt.return (Ok ())
544555

@@ -601,7 +612,7 @@ struct
601612
in
602613
let callback = match !http with
603614
| None -> None
604-
| Some http -> Http_forwarder.handle ~dst:(local_ip, local_port) ~t:http
615+
| Some http -> Http_forwarder.transparent_proxy_handler ~dst:(local_ip, local_port) ~t:http
605616
in
606617
begin match callback with
607618
| None ->

Diff for: src/hostnet_test/test_http.ml

+78-38
Original file line numberDiff line numberDiff line change
@@ -178,32 +178,6 @@ let test_interception () =
178178
Lwt.return ()
179179
end
180180

181-
(* Test that port 3128 passes through to an upstream proxy *)
182-
let test_proxy_passthrough () =
183-
Host.Main.run begin
184-
let request =
185-
Cohttp.Request.make
186-
(Uri.make ~scheme:"http" ~host:"dave.recoil.org" ~path:"/" ())
187-
in
188-
intercept ~pcap:"test_proxy_passthrough.pcap" ~port:3128 request >>= fun result ->
189-
Log.info (fun f ->
190-
f "original was: %s"
191-
(Sexplib.Sexp.to_string_hum (Cohttp.Request.sexp_of_t request)));
192-
Log.info (fun f ->
193-
f "proxied was: %s"
194-
(Sexplib.Sexp.to_string_hum (Cohttp.Request.sexp_of_t result)));
195-
Alcotest.check Alcotest.string "resource"
196-
request.Cohttp.Request.resource
197-
result.Cohttp.Request.resource;
198-
Alcotest.check Alcotest.string "method"
199-
(Cohttp.Code.string_of_method request.Cohttp.Request.meth)
200-
(Cohttp.Code.string_of_method result.Cohttp.Request.meth);
201-
Alcotest.check Alcotest.string "version"
202-
(Cohttp.Code.string_of_version request.Cohttp.Request.version)
203-
(Cohttp.Code.string_of_version result.Cohttp.Request.version);
204-
Lwt.return ()
205-
end
206-
207181
(* Test that a relative URI becomes absolute *)
208182
let test_uri_relative () =
209183
Host.Main.run begin
@@ -293,6 +267,72 @@ let test_user_agent_preserved () =
293267

294268
let err_flush e = Fmt.kstrf failwith "%a" Incoming.C.pp_write_error e
295269

270+
let test_proxy_passthrough () =
271+
let forwarded, forwarded_u = Lwt.task () in
272+
Host.Main.run begin
273+
Slirp_stack.with_stack ~pcap:"test_proxy_passthrough.pcap" (fun _ stack ->
274+
with_server (fun flow ->
275+
let ic = Incoming.C.create flow in
276+
(* read something *)
277+
Incoming.C.read_some ~len:5 ic
278+
>>= function
279+
| Ok `Eof -> failwith "test_proxy_passthrough: read_some returned Eof"
280+
| Error _ -> failwith "test_proxy_passthrough: read_some returned Error"
281+
| Ok (`Data buf) ->
282+
let txt = Cstruct.to_string buf in
283+
Alcotest.check Alcotest.string "message" "hello" txt;
284+
let response = "there" in
285+
(* write something *)
286+
Incoming.C.write_string ic response 0 (String.length response);
287+
Incoming.C.flush ic
288+
>>= function
289+
| Error _ -> failwith "test_proxy_passthrough: flush returned error"
290+
| Ok () ->
291+
Lwt.wakeup_later forwarded_u ();
292+
Lwt.return_unit
293+
) (fun server ->
294+
let json =
295+
Ezjsonm.from_string (" { \"http\": \"127.0.0.1:" ^
296+
(string_of_int server.Server.port) ^ "\" }")
297+
in
298+
Slirp_stack.Slirp_stack.Debug.update_http_json json ()
299+
>>= function
300+
| Error (`Msg m) -> failwith ("Failed to enable HTTP proxy: " ^ m)
301+
| Ok () ->
302+
let open Slirp_stack in
303+
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (primary_dns_ip, 3128)
304+
>>= function
305+
| Error _ ->
306+
Log.err (fun f -> f "Failed to connect to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
307+
failwith "test_proxy_passthrough: connect failed"
308+
| Ok flow ->
309+
Log.info (fun f -> f "Connected to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
310+
let oc = Outgoing.C.create flow in
311+
let request = "hello" in
312+
Outgoing.C.write_string oc request 0 (String.length request);
313+
Outgoing.C.flush oc
314+
>>= function
315+
| Error _ -> failwith "test_proxy_passthrough: client flush returned error"
316+
| Ok () ->
317+
Outgoing.C.read_some ~len:5 oc
318+
>>= function
319+
| Ok `Eof -> failwith "test_proxy_passthrough: client read_some returned Eof"
320+
| Error _ -> failwith "test_proxy_passthrough: client read_some returned Error"
321+
| Ok (`Data buf) ->
322+
let txt = Cstruct.to_string buf in
323+
Alcotest.check Alcotest.string "message" "there" txt;
324+
Lwt.pick [
325+
(Host.Time.sleep_ns (Duration.of_sec 100) >|= fun () ->
326+
`Timeout);
327+
(forwarded >>= fun x -> Lwt.return (`Result x))
328+
]
329+
)
330+
>|= function
331+
| `Timeout -> failwith "HTTP proxy failed"
332+
| `Result x -> x
333+
)
334+
end
335+
296336
let test_http_connect () =
297337
let test_dst_ip = Ipaddr.V4.of_string_exn "1.2.3.4" in
298338
Host.Main.run begin
@@ -387,13 +427,13 @@ let test_http_connect () =
387427
| Error (`Msg m) -> failwith ("Failed to enable HTTP proxy: " ^ m)
388428
| Ok () ->
389429
let open Slirp_stack in
390-
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (Ipaddr.V4.localhost, 3128)
430+
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (primary_dns_ip, 3128)
391431
>>= function
392432
| Error _ ->
393-
Log.err (fun f -> f "Failed to connect to localhost:3128");
433+
Log.err (fun f -> f "Failed to connect to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
394434
failwith "test_proxy_connect: connect failed"
395435
| Ok flow ->
396-
Log.info (fun f -> f "Connected to localhost:80");
436+
Log.info (fun f -> f "Connected to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
397437
let oc = Outgoing.C.create flow in
398438
let request =
399439
let connect = Cohttp.Request.make ~meth:`CONNECT (Uri.make ()) in
@@ -440,13 +480,13 @@ let test_http_connect () =
440480
Host.Main.run begin
441481
Slirp_stack.with_stack ~pcap:"test_http_proxy_connect_fail.pcap" (fun _ stack ->
442482
let open Slirp_stack in
443-
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (Ipaddr.V4.localhost, 3128)
483+
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (primary_dns_ip, 3128)
444484
>>= function
445485
| Error _ ->
446-
Log.err (fun f -> f "Failed to connect to localhost:3128");
486+
Log.err (fun f -> f "Failed to connect to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
447487
failwith "test_proxy_connect_fail: connect failed"
448488
| Ok flow ->
449-
Log.info (fun f -> f "Connected to localhost:80");
489+
Log.info (fun f -> f "Connected to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
450490
let oc = Outgoing.C.create flow in
451491
let request =
452492
let connect = Cohttp.Request.make ~meth:`CONNECT (Uri.make ()) in
@@ -475,13 +515,13 @@ let test_http_connect () =
475515
Host.Main.run begin
476516
Slirp_stack.with_stack ~pcap:"test_http_proxy_get_dns.pcap" (fun _ stack ->
477517
let open Slirp_stack in
478-
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (Ipaddr.V4.localhost, 3128)
518+
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (primary_dns_ip, 3128)
479519
>>= function
480520
| Error _ ->
481-
Log.err (fun f -> f "Failed to connect to localhost:3128");
521+
Log.err (fun f -> f "Failed to connect to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
482522
failwith "test_proxy_get_dns: connect failed"
483523
| Ok flow ->
484-
Log.info (fun f -> f "Connected to localhost:80");
524+
Log.info (fun f -> f "Connected to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
485525
let oc = Outgoing.C.create flow in
486526
let host = "does.not.exist.recoil.org" in
487527
let request = Cohttp.Request.make ~meth:`GET (Uri.make ~host ()) in
@@ -506,13 +546,13 @@ let test_http_connect () =
506546
Host.Main.run begin
507547
Slirp_stack.with_stack ~pcap:"test_http_proxy_get.pcap" (fun _ stack ->
508548
let open Slirp_stack in
509-
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (Ipaddr.V4.localhost, 3128)
549+
Client.TCPV4.create_connection (Client.tcpv4 stack.t) (primary_dns_ip, 3128)
510550
>>= function
511551
| Error _ ->
512-
Log.err (fun f -> f "Failed to connect to localhost:3128");
552+
Log.err (fun f -> f "Failed to connect to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
513553
failwith "test_proxy_get: connect failed"
514554
| Ok flow ->
515-
Log.info (fun f -> f "Connected to localhost:80");
555+
Log.info (fun f -> f "Connected to %s:3128" (Ipaddr.V4.to_string primary_dns_ip));
516556
let oc = Outgoing.C.create flow in
517557
let host = "dave.recoil.org" in
518558
let request = Cohttp.Request.make ~meth:`GET (Uri.make ~host ()) in

0 commit comments

Comments
 (0)