mte/unikernel/duniverse/mirage-tcpip/test/test_keepalive.ml
2025-11-11 02:07:51 +01:00

142 lines
5.7 KiB
OCaml

(* Test the functional part *)
(* Linux default *)
let default = Tcpip.Tcp.Keepalive.({
after = Duration.of_sec 7200; (* 2 hours *)
interval = Duration.of_sec 75; (* 75 seconds *)
probes = 9;
})
let simulate configuration iterations nprobes ns state =
let rec loop iterations nprobes ns state =
if iterations > 3 * configuration.Tcpip.Tcp.Keepalive.probes
then Alcotest.fail (Printf.sprintf "too many iteractions: loop in keep-alive test? iterations = %d nprobes = %d ns=%Ld" iterations nprobes ns);
let action, state' = Tcp.Keepalive.next ~configuration ~ns state in
match action with
| `SendProbe ->
Logs.info (fun f -> f "iteration %d, ns %Ld: SendProbe" iterations ns);
loop (iterations + 1) (nprobes + 1) ns state'
| `Wait ns' ->
Logs.info (fun f -> f "iteration %d, ns %Ld: Wait %Ld" iterations ns ns');
loop (iterations + 1) nprobes (Int64.add ns ns') state'
| `Close ->
Logs.info (fun f -> f "iteration %d, ns %Ld: Close" iterations ns);
nprobes in
loop iterations nprobes ns state
(* check we send the expected number of probes if everything does as expected *)
let test_keepalive_sequence () =
let configuration = default in
let state = Tcp.Keepalive.alive in
let nprobes = simulate configuration 0 0 0L state in
Alcotest.(check int) "number of probes" (configuration.probes) nprobes
(* check what happens if we miss a probe *)
let test_keepalive_miss_probes () =
let configuration = default in
let state = Tcp.Keepalive.alive in
(* skip sending the first 1 or 2 probes *)
let ns = Int64.(add configuration.Tcpip.Tcp.Keepalive.after (mul 2L configuration.Tcpip.Tcp.Keepalive.interval)) in
let nprobes = simulate configuration 0 0 ns state in
if nprobes >= configuration.Tcpip.Tcp.Keepalive.probes
then Alcotest.fail (Printf.sprintf "too many probes: max was %d but we sent %d and we should have skipped the first 1 or 2" configuration.probes nprobes)
(* check what happens if we exceed the maximum timeout *)
let test_keepalive_miss_everything () =
let configuration = default in
let state = Tcp.Keepalive.alive in
(* massive delay *)
let ns = Int64.(add configuration.Tcpip.Tcp.Keepalive.after (mul 2L (mul (of_int configuration.Tcpip.Tcp.Keepalive.probes) configuration.Tcpip.Tcp.Keepalive.interval))) in
let nprobes = simulate configuration 0 0 ns state in
if nprobes <> 0
then Alcotest.fail (Printf.sprintf "too many probes: max was %d but we sent %d and we should have skipped all" configuration.probes nprobes)
let suite_1 = [
"correct number of keepalives", `Quick, test_keepalive_sequence;
"we don't try to send old keepalives", `Quick, test_keepalive_miss_probes;
"check we close if we miss all probes", `Slow, test_keepalive_miss_everything;
]
let suite_1 =
List.map (fun (n, s, f) -> n, s, (fun () -> Lwt.return (f ()))) suite_1
(* Test the end-to-end protocol behaviour *)
open Common
open Vnetif_common
let (>>=) = Lwt.(>>=)
let src = Logs.Src.create "test_keepalive" ~doc:"keepalive tests"
module Log = (val Logs.src_log src : Logs.LOG)
(* Establish a TCP connection, enable keepalives on the connection, tell the network
to drop all packets and check that the keep-alives detect the failure. *)
module Test_connect = struct
module V = VNETIF_STACK (Vnetif_backends.On_off_switch)
let gateway = Ipaddr.V4.of_string_exn "10.0.0.1"
let client_cidr = Ipaddr.V4.Prefix.of_string_exn "10.0.0.101/24"
let server_cidr = Ipaddr.V4.Prefix.of_string_exn "10.0.0.100/24"
let backend = V.create_backend ()
let err_read_eof () = failf "accept got EOF while reading"
let err_write_eof () = failf "client tried to write, got EOF"
let err_read e =
let err = Format.asprintf "%a" V.Stack.TCP.pp_error e in
failf "Error while reading: %s" err
let accept flow =
let ip, port = V.Stack.TCP.dst flow in
Logs.debug (fun f -> f "Accepted connection from %s:%d" (Ipaddr.to_string ip) port);
V.Stack.TCP.read flow >>= function
| Error e -> err_read e
| Ok `Eof -> Lwt.return_unit
| Ok (`Data _) -> failf "accept: expected to get EOF in read, but got data"
let test_tcp_keepalive_timeout () =
let timeout = 15.0 in
Lwt.pick [
(Lwt_unix.sleep timeout >>= fun () ->
failf "connect test timedout after %f seconds" timeout) ;
(V.create_stack ~cidr:server_cidr ~gateway backend >>= fun s1 ->
V.Stack.TCP.listen (V.Stack.tcp s1) ~port:80 (fun f -> accept f);
V.Stack.listen s1) ;
(Lwt_unix.sleep 0.1 >>= fun () ->
V.create_stack ~cidr:client_cidr ~gateway backend >>= fun s2 ->
Lwt.pick [
V.Stack.listen s2;
let keepalive = { Tcpip.Tcp.Keepalive.after = 0L; interval = Duration.of_sec 1; probes = 3 } in
(let conn = V.Stack.TCP.create_connection ~keepalive (V.Stack.tcp s2) in
or_error "connect" conn (Ipaddr.V4 (Ipaddr.V4.Prefix.address server_cidr), 80) >>= fun flow ->
Logs.debug (fun f -> f "Connected to other end...");
Vnetif_backends.On_off_switch.send_packets := false;
V.Stack.TCP.read flow >>= function
| Error e -> err_read e
| Ok (`Data _) -> failf "read: expected to get EOF, but got data"
| Ok `Eof ->
Logs.debug (fun f -> f "connection read EOF as expected");
V.Stack.TCP.close flow >>= fun () ->
Lwt_unix.sleep 1.0 >>= fun () -> (* record some traffic after close *)
Lwt.return_unit)]) ] >>= fun () ->
Lwt.return_unit
let record_pcap =
V.record_pcap backend
end
let test_tcp_keepalive_timeout () =
Test_connect.record_pcap
"test_tcp_keepalive_timeout.pcap"
Test_connect.test_tcp_keepalive_timeout
let suite_2 = [
"check that TCP keepalives detect a network failure", `Slow,
test_tcp_keepalive_timeout;
]
let suite = suite_1 @ suite_2