let default_window () = Tcp.Window.t ~tx_wnd_scale:2 ~rx_wnd_scale:2 ~rx_wnd:65535 ~tx_wnd:65535 ~rx_isn:Tcp.Sequence.zero ~tx_mss:1460 ~tx_isn:Tcp.Sequence.zero let fresh_window () = let window = default_window () in Alcotest.(check bool) "should be no data in flight" false @@ Tcp.Window.tx_inflight window; Alcotest.(check bool) "no rexmits yet" false @@ Tcp.Window.max_rexmits_done window; Alcotest.(check int) "no traffic transferred yet" 0 @@ Tcp.Window.tx_totalbytes window; Alcotest.(check int) "no traffic received yet" 0 @@ Tcp.Window.rx_totalbytes window; Alcotest.(check int32) "should be able to send 65535 <<= 2 bytes" Int32.(mul 65535l 4l) @@ Tcp.Window.tx_wnd window; Alcotest.(check int32) "should be able to receive 65535 <<= 2 bytes" Int32.(mul 65535l 4l) @@ Tcp.Window.rx_wnd window; Alcotest.(check int64) "initial rto is 2/3 second" (Duration.of_ms 667) @@ Tcp.Window.rto window; Lwt.return_unit let increase_congestion_window window goal = (* simulate a successful slow start, which primes the congestion window to be relatively large *) let receive_window = Tcp.Window.ack_win window in let rec successful_transmission goal = let max_send = Tcp.Window.tx_available window |> Tcp.Sequence.of_int32 in match Tcp.Sequence.geq max_send goal with | true -> max_send | false -> let sz = Tcp.Sequence.add max_send @@ Tcp.Window.tx_nxt window in Mirage_mtime_set.tick (); Tcp.Window.tx_advance window @@ Tcp.Window.tx_nxt window; Mirage_mtime_set.tick (); (* need to acknowledge the full size of the data *) Tcp.Window.tx_ack window sz receive_window; successful_transmission goal in successful_transmission goal let n_segments window n = Int32.mul n @@ Int32.of_int @@ Tcp.Window.tx_mss window |> Tcp.Sequence.of_int32 (* attempt to ensure that fast recovery is working as described in rfc5681 *) let recover_fast () = let window = default_window () in let receive_window = Tcp.Window.ack_win window in Alcotest.(check bool) "don't start in fast recovery" false @@ Tcp.Window.fast_rec window; (* get a large congestion window to avoid confounding factors *) let cwnd_goal = 262140l in let _ = increase_congestion_window window (Tcp.Sequence.of_int32 cwnd_goal) in let available_to_send = Tcp.Window.tx_available window in let big_enough x = Int32.compare x cwnd_goal > 0 in Alcotest.(check bool) "congestion window is big enough" true @@ big_enough available_to_send; (* get ready to send another burst of data *) let seq = Tcp.Window.tx_nxt window in Mirage_mtime_set.tick (); (* say that we sent the full amount of data *) let sz = Tcp.Sequence.(add (of_int32 available_to_send) seq) in Tcp.Window.tx_advance window sz; (* but receive an ack indicating that we missed a segment *) let nonfull_ack = Tcp.Sequence.add seq @@ n_segments window 4l in (* 1st ack *) Mirage_mtime_set.tick (); Tcp.Window.tx_ack window nonfull_ack receive_window; (* 1st duplicate ack *) Mirage_mtime_set.tick (); Tcp.Window.tx_ack window nonfull_ack receive_window; (* 2nd duplicate ack *) Mirage_mtime_set.tick (); Tcp.Window.tx_ack window nonfull_ack receive_window; (* 3rd duplicate ack *) Mirage_mtime_set.tick (); Tcp.Window.tx_ack window nonfull_ack receive_window; (* request that we go into fast retransmission *) Tcp.Window.alert_fast_rexmit window @@ n_segments window 4l; Alcotest.(check bool) "fast retransmit when we wanted it" true @@ Tcp.Window.fast_rec window; Alcotest.(check bool) "once entering fast recovery, we can send >0 packets" true ((Int32.compare (Tcp.Window.tx_available window) 0l) > 0); Lwt.return_unit let rto_calculation () = let window = default_window () in (* RFC 2988 2.1 *) Alcotest.(check int64) "initial rto is 2/3 second" (Duration.of_ms 667) @@ Tcp.Window.rto window; let receive_window = Tcp.Window.ack_win window in Tcp.Window.tx_advance window (Tcp.Window.tx_nxt window); Mirage_mtime_set.tick_for (Duration.of_ms 400); let max_size = Tcp.Window.tx_available window |> Tcp.Sequence.of_int32 in let sz = Tcp.Sequence.add max_size @@ (Tcp.Window.tx_nxt window) in Tcp.Window.tx_ack window sz receive_window; (* RFC 2988 2.2 *) Alcotest.(check int64) "After one RTT measurement, the calculated rto is 400 + (4 * 200) = 1200ms" (Duration.of_ms 1200) @@ Tcp.Window.rto window; (* RFC 2988 2.3 *) Tcp.Window.tx_advance window (Tcp.Window.tx_nxt window); let receive_window = Tcp.Window.ack_win window in Mirage_mtime_set.tick_for (Duration.of_ms 300); let max_size = Tcp.Window.tx_available window |> Tcp.Sequence.of_int32 in let sz = Tcp.Sequence.add max_size @@ (Tcp.Window.tx_nxt window) in Tcp.Window.tx_ack window sz receive_window; Alcotest.(check int64) "After subsequent RTT measurement, the calculated rto is 1087.5ms" (Duration.of_us 1087500) @@ Tcp.Window.rto window; Lwt.return_unit let suite = [ "fresh window is sensible", `Quick, fresh_window; "fast recovery recovers fast", `Quick, recover_fast; "smoothed rtt, rtt variation and retransmission timer are calculated according to RFC2988", `Quick, rto_calculation; ] let suite = [ "tcp_window" , suite ; ] let run test () = Lwt_main.run (test ()) let () = Printexc.record_backtrace true; Mirage_crypto_rng_unix.use_default (); (* enable logging to stdout for all modules *) Logs.set_reporter (Logs_fmt.reporter ()); Logs.set_level ~all:true (Some Logs.Debug); let suite = List.map (fun (n, s) -> n, List.map (fun (d, s, f) -> d, s, run f) s ) suite in Alcotest.run "tcpip" suite