213 lines
6.6 KiB
OCaml
213 lines
6.6 KiB
OCaml
|
|
open Lwt.Infix
|
||
|
|
|
||
|
|
let ( <.> ) f g x = f (g x)
|
||
|
|
let ( >>? ) = Lwt_result.bind
|
||
|
|
|
||
|
|
let reporter ppf =
|
||
|
|
let report src level ~over k msgf =
|
||
|
|
let k _ =
|
||
|
|
over () ;
|
||
|
|
k () in
|
||
|
|
let with_metadata header _tags k ppf fmt =
|
||
|
|
Format.kfprintf k ppf
|
||
|
|
("%a[%a]: " ^^ fmt ^^ "\n%!")
|
||
|
|
Logs_fmt.pp_header (level, header)
|
||
|
|
Fmt.(styled `Magenta string)
|
||
|
|
(Logs.Src.name src) in
|
||
|
|
msgf @@ fun ?header ?tags fmt -> with_metadata header tags k ppf fmt in
|
||
|
|
{ Logs.report }
|
||
|
|
|
||
|
|
let () = Fmt_tty.setup_std_outputs ~style_renderer:`Ansi_tty ~utf_8:true ()
|
||
|
|
let () = Logs.set_reporter (reporter Fmt.stderr)
|
||
|
|
let () = Logs.set_level ~all:true (Some Logs.Debug)
|
||
|
|
let () = Mirage_crypto_rng_unix.use_default ()
|
||
|
|
|
||
|
|
module P = Paf_mirage.Make (Tcpip_stack_socket.V4V6.TCP)
|
||
|
|
|
||
|
|
let unix_stack () =
|
||
|
|
Tcpip_stack_socket.V4V6.UDP.connect ~ipv4_only:false ~ipv6_only:false
|
||
|
|
Ipaddr.V4.Prefix.global None
|
||
|
|
>>= fun udpv4 ->
|
||
|
|
Tcpip_stack_socket.V4V6.TCP.connect ~ipv4_only:false ~ipv6_only:false
|
||
|
|
Ipaddr.V4.Prefix.global None
|
||
|
|
>>= fun tcpv4 -> Tcpip_stack_socket.V4V6.connect udpv4 tcpv4
|
||
|
|
|
||
|
|
let load_file filename =
|
||
|
|
let ic = open_in filename in
|
||
|
|
let ln = in_channel_length ic in
|
||
|
|
let rs = Bytes.create ln in
|
||
|
|
really_input ic rs 0 ln ;
|
||
|
|
close_in ic ;
|
||
|
|
Bytes.unsafe_to_string rs
|
||
|
|
|
||
|
|
let tls =
|
||
|
|
let cert = load_file "server.pem" in
|
||
|
|
let key = load_file "server.key" in
|
||
|
|
match
|
||
|
|
(X509.Certificate.decode_pem_multiple cert, X509.Private_key.decode_pem key)
|
||
|
|
with
|
||
|
|
| Ok certs, Ok (`RSA key) ->
|
||
|
|
Result.get_ok
|
||
|
|
(Tls.Config.server ~alpn_protocols:[ "http/1.1"; "h2" ]
|
||
|
|
~certificates:(`Single (certs, `RSA key))
|
||
|
|
())
|
||
|
|
| _ -> invalid_arg "Invalid certificate or key"
|
||
|
|
|
||
|
|
let alpn_of_tls_connection (_, flow) =
|
||
|
|
match P.TLS.epoch flow with
|
||
|
|
| Ok { Tls.Core.alpn_protocol; _ } ->
|
||
|
|
Fmt.epr ">>> alpn_protocol (server side): %a.\n%!"
|
||
|
|
Fmt.(option string)
|
||
|
|
alpn_protocol ;
|
||
|
|
alpn_protocol
|
||
|
|
| Error _ -> None
|
||
|
|
|
||
|
|
let peer_of_tls_connection ((ipaddr, port), _) =
|
||
|
|
Fmt.str "%a:%d" Ipaddr.pp ipaddr port
|
||
|
|
|
||
|
|
let injection =
|
||
|
|
let module R = (val Mimic.repr P.tls_protocol) in
|
||
|
|
fun (_, flow) -> R.T flow
|
||
|
|
|
||
|
|
let port =
|
||
|
|
let v = ref 9999 in
|
||
|
|
fun () ->
|
||
|
|
incr v ;
|
||
|
|
!v
|
||
|
|
|
||
|
|
let service handler () =
|
||
|
|
let info =
|
||
|
|
{
|
||
|
|
Alpn.alpn = alpn_of_tls_connection;
|
||
|
|
Alpn.peer = peer_of_tls_connection;
|
||
|
|
Alpn.injection;
|
||
|
|
} in
|
||
|
|
let handshake flow =
|
||
|
|
let edn = P.TCP.dst flow in
|
||
|
|
P.TLS.server_of_flow tls flow >>= function
|
||
|
|
| Ok flow -> Lwt.return_ok (edn, flow)
|
||
|
|
| Error err ->
|
||
|
|
Lwt.return_error (`Msg (Fmt.str "%a" P.TLS.pp_write_error err))
|
||
|
|
and close = P.close in
|
||
|
|
Alpn.service info handler handshake P.accept close
|
||
|
|
|
||
|
|
module R = (val Mimic.repr P.tls_protocol)
|
||
|
|
|
||
|
|
type version = HTTP_1_1 | HTTP_2_0
|
||
|
|
|
||
|
|
let error_handler _ _protocol ?request:_ _error _response = ()
|
||
|
|
|
||
|
|
let request_handler : type reqd headers request response ro wo.
|
||
|
|
_ ->
|
||
|
|
_ ->
|
||
|
|
_ ->
|
||
|
|
_ ->
|
||
|
|
reqd ->
|
||
|
|
(reqd, headers, request, response, ro, wo) Alpn.protocol ->
|
||
|
|
unit =
|
||
|
|
fun wk_request wk _flow _edn _reqd -> function
|
||
|
|
| Alpn.HTTP_1_1 (module Reqd) ->
|
||
|
|
Lwt.wakeup_later wk_request HTTP_1_1 ;
|
||
|
|
Lwt.wakeup_later wk ()
|
||
|
|
| Alpn.H2 (module Reqd) ->
|
||
|
|
Lwt.wakeup_later wk_request HTTP_2_0 ;
|
||
|
|
Lwt.wakeup_later wk ()
|
||
|
|
|
||
|
|
let server_handler wk_request wk =
|
||
|
|
{
|
||
|
|
Alpn.error = error_handler;
|
||
|
|
Alpn.request =
|
||
|
|
(fun flow edn reqd protocol ->
|
||
|
|
request_handler wk_request wk flow edn reqd protocol);
|
||
|
|
}
|
||
|
|
|
||
|
|
let client ~ctx handler req =
|
||
|
|
Mimic.resolve ctx >>= function
|
||
|
|
| Error err -> Alcotest.failf "%a" Mimic.pp_error err
|
||
|
|
| Ok (R.T v as flow) -> (
|
||
|
|
let alpn =
|
||
|
|
match P.TLS.epoch v with
|
||
|
|
| Ok { Tls.Core.alpn_protocol; _ } -> alpn_protocol
|
||
|
|
| Error _ -> None in
|
||
|
|
Alpn.run ?alpn handler () req flow >>= function
|
||
|
|
| Ok body -> Lwt.return body
|
||
|
|
| Error err -> Alcotest.failf "%a" Mimic.pp_error err)
|
||
|
|
| Ok flow -> (
|
||
|
|
Alpn.run handler () req flow >>= function
|
||
|
|
| Ok body -> Lwt.return body
|
||
|
|
| Error err -> Alcotest.failf "%a" Mimic.pp_error err)
|
||
|
|
|
||
|
|
let ctx_with_tls stack ~port tls =
|
||
|
|
let ipaddr = Ipaddr_unix.of_inet_addr Unix.inet_addr_loopback in
|
||
|
|
Mimic.add P.tls_edn (None, tls, stack, ipaddr, port) Mimic.empty
|
||
|
|
|
||
|
|
let authenticator ?ip:_ ~host:_ _ = Ok None
|
||
|
|
let apply v f = f v
|
||
|
|
|
||
|
|
let fake_client_handler =
|
||
|
|
{
|
||
|
|
Alpn.error = (fun _ _protocol _error -> ());
|
||
|
|
Alpn.response = (fun _flow _edn _response _body _protocol -> ());
|
||
|
|
}
|
||
|
|
|
||
|
|
let test01 =
|
||
|
|
Alcotest_lwt.test_case "http/1.1" `Quick @@ fun _sw () ->
|
||
|
|
let port = port () in
|
||
|
|
let stop = Lwt_switch.create () in
|
||
|
|
let th, wk = Lwt.wait () in
|
||
|
|
let request, wk_request = Lwt.wait () in
|
||
|
|
let service = service (server_handler wk_request wk) () in
|
||
|
|
let tls =
|
||
|
|
Result.get_ok
|
||
|
|
(Tls.Config.client ~authenticator ~alpn_protocols:[ "http/1.1" ] ()) in
|
||
|
|
let req = `V1 (H1.Request.create `GET "/") in
|
||
|
|
Lwt.both
|
||
|
|
( unix_stack () >|= Tcpip_stack_socket.V4V6.tcp >>= fun stack ->
|
||
|
|
P.init ~port stack >>= fun t ->
|
||
|
|
P.serve ~stop service t |> fun (`Initialized th) ->
|
||
|
|
let ctx = ctx_with_tls stack ~port tls in
|
||
|
|
Lwt.both (client ~ctx fake_client_handler req) th )
|
||
|
|
(th >>= fun () -> Lwt_switch.turn_off stop)
|
||
|
|
>>= fun ((body, ()), ()) ->
|
||
|
|
request >>= fun request ->
|
||
|
|
match (request, body) with
|
||
|
|
| HTTP_1_1, Alpn.Response_HTTP_1_1 _ ->
|
||
|
|
Alcotest.(check pass) "http/1.1" () () ;
|
||
|
|
Lwt.return_unit
|
||
|
|
| _ -> Alcotest.failf "Unexpected version of HTTP"
|
||
|
|
|
||
|
|
let close_body = function
|
||
|
|
| Alpn.Response_HTTP_1_1 _ as response -> response
|
||
|
|
| Alpn.Response_H2 (body, _) as response ->
|
||
|
|
H2.Body.Writer.close body ;
|
||
|
|
response
|
||
|
|
|
||
|
|
let test02 =
|
||
|
|
Alcotest_lwt.test_case "h2" `Quick @@ fun _sw () ->
|
||
|
|
let port = port () in
|
||
|
|
let stop = Lwt_switch.create () in
|
||
|
|
let th, wk = Lwt.wait () in
|
||
|
|
let request, wk_request = Lwt.wait () in
|
||
|
|
let service = service (server_handler wk_request wk) () in
|
||
|
|
let tls =
|
||
|
|
Result.get_ok (Tls.Config.client ~authenticator ~alpn_protocols:[ "h2" ] ())
|
||
|
|
in
|
||
|
|
let req = `V2 (H2.Request.create ~scheme:"https" `GET "/") in
|
||
|
|
Lwt.both
|
||
|
|
( unix_stack () >|= Tcpip_stack_socket.V4V6.tcp >>= fun stack ->
|
||
|
|
P.init ~port stack >>= fun t ->
|
||
|
|
P.serve ~stop service t |> fun (`Initialized th) ->
|
||
|
|
let ctx = ctx_with_tls stack ~port tls in
|
||
|
|
Lwt.both (client ~ctx fake_client_handler req >|= close_body) th )
|
||
|
|
(th >>= fun () -> Lwt_switch.turn_off stop)
|
||
|
|
>>= fun ((body, ()), ()) ->
|
||
|
|
request >>= fun request ->
|
||
|
|
match (request, body) with
|
||
|
|
| HTTP_2_0, Alpn.Response_H2 _ ->
|
||
|
|
Alcotest.(check pass) "h2" () () ;
|
||
|
|
Lwt.return_unit
|
||
|
|
| _ -> Alcotest.failf "Unexpected version of HTTP"
|
||
|
|
|
||
|
|
let test () = Alcotest_lwt.run "alpn" [ ("alpn", [ test01; test02 ]) ]
|
||
|
|
let () = Lwt_main.run (test ())
|