142 lines
5.7 KiB
OCaml
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
|