This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
284
unikernel/duniverse/mirage-tcpip/test/test_tcp_options.ml
Normal file
284
unikernel/duniverse/mirage-tcpip/test/test_tcp_options.ml
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
open Common
|
||||
|
||||
let check = Alcotest.(check @@ result (list options) string)
|
||||
|
||||
let errors ?(check_msg = false) exp = function
|
||||
| Ok opt -> failf "Ok %a when Error %s expected" Tcp.Options.pps opt exp
|
||||
| Error p -> if check_msg then
|
||||
Alcotest.(check string)
|
||||
"Error didn't give the expected error message" exp p
|
||||
else ()
|
||||
|
||||
let test_unmarshal_bad_mss () =
|
||||
let odd_sized_mss = Cstruct.create 3 in
|
||||
Cstruct.set_uint8 odd_sized_mss 0 2;
|
||||
Cstruct.set_uint8 odd_sized_mss 1 3;
|
||||
Cstruct.set_uint8 odd_sized_mss 2 255;
|
||||
errors "MSS size is unreasonable" (Tcp.Options.unmarshal odd_sized_mss)
|
||||
|
||||
let test_unmarshal_bogus_length () =
|
||||
let bogus = Cstruct.create (4*8-1) in
|
||||
Cstruct.memset bogus 0;
|
||||
Cstruct.blit_from_string "\x6e\x73\x73\x68\x2e\x63\x6f\x6d" 0 bogus 0 8;
|
||||
(* some unknown option (0x6e) with claimed length 0x73, longer than
|
||||
the buffer. This invalidates later results, but previous ones are
|
||||
still valid, if any *)
|
||||
check "length" (Ok []) (Tcp.Options.unmarshal bogus)
|
||||
|
||||
let test_unmarshal_zero_length () =
|
||||
let bogus = Cstruct.create 10 in
|
||||
Cstruct.memset bogus 1; (* noops *)
|
||||
Cstruct.set_uint8 bogus 0 64; (* arbitrary unknown option-kind *)
|
||||
Cstruct.set_uint8 bogus 1 0;
|
||||
(* this invalidates later results, but previous ones are still
|
||||
valid, if any *)
|
||||
check "zero" (Ok []) (Tcp.Options.unmarshal bogus)
|
||||
|
||||
let test_unmarshal_simple_options () =
|
||||
(* empty buffer should give empty list *)
|
||||
check "simple" (Ok []) (Tcp.Options.unmarshal (Cstruct.create 0));
|
||||
|
||||
(* buffer with just eof should give empty list *)
|
||||
let just_eof = Cstruct.create 1 in
|
||||
Cstruct.set_uint8 just_eof 0 0;
|
||||
check "eof" (Ok []) (Tcp.Options.unmarshal just_eof);
|
||||
|
||||
(* buffer with single noop should give a list with 1 noop *)
|
||||
let just_noop = Cstruct.create 1 in
|
||||
Cstruct.set_uint8 just_noop 0 1;
|
||||
check "noop" (Ok [ Tcp.Options.Noop ]) (Tcp.Options.unmarshal just_noop);
|
||||
|
||||
(* buffer with valid, but unknown, option should be correctly communicated *)
|
||||
let unknown = Cstruct.create 10 in
|
||||
let data = "hi mom!!" in
|
||||
let kind = 18 in (* TODO: more canonically unknown option-kind *)
|
||||
Cstruct.blit_from_string data 0 unknown 2 (String.length data);
|
||||
Cstruct.set_uint8 unknown 0 kind;
|
||||
Cstruct.set_uint8 unknown 1 (Cstruct.length unknown);
|
||||
check "more"
|
||||
(Ok [Tcp.Options.Unknown (kind, data)])
|
||||
(Tcp.Options.unmarshal unknown)
|
||||
|
||||
let test_unmarshal_stops_at_eof () =
|
||||
let buf = Cstruct.create 14 in
|
||||
let ts1 = 0xabad1deal in
|
||||
let ts2 = 0xc0ffee33l in
|
||||
Cstruct.memset buf 0;
|
||||
Cstruct.set_uint8 buf 0 4; (* sack_ok *)
|
||||
Cstruct.set_uint8 buf 1 2; (* length of two *)
|
||||
Cstruct.set_uint8 buf 2 1; (* noop *)
|
||||
Cstruct.set_uint8 buf 3 0; (* eof *)
|
||||
Cstruct.set_uint8 buf 4 8; (* timestamp *)
|
||||
Cstruct.set_uint8 buf 5 10; (* timestamps are 2 4-byte times *)
|
||||
Cstruct.BE.set_uint32 buf 6 ts1;
|
||||
Cstruct.BE.set_uint32 buf 10 ts2;
|
||||
(* correct parsing will ignore options from after eof, so we shouldn't see
|
||||
timestamp or noop *)
|
||||
match Tcp.Options.unmarshal buf with
|
||||
| Error s -> Alcotest.fail s
|
||||
| Ok result ->
|
||||
Alcotest.(check bool) "SACK_ok missing"
|
||||
true (List.mem Tcp.Options.SACK_ok result);
|
||||
Alcotest.(check bool) "noop missing"
|
||||
true (List.mem Tcp.Options.Noop result);
|
||||
Alcotest.(check bool) "timestamp present"
|
||||
false (List.mem (Tcp.Options.Timestamp (ts1, ts2)) result)
|
||||
|
||||
let test_unmarshal_ok_options () =
|
||||
let buf = Cstruct.create 8 in
|
||||
Cstruct.memset buf 0;
|
||||
let opts = [ Tcp.Options.MSS 536; Tcp.Options.SACK_ok; Tcp.Options.Noop;
|
||||
Tcp.Options.Noop ] in
|
||||
let marshalled = Tcp.Options.marshal buf opts in
|
||||
Alcotest.(check int) "marshalled" marshalled 8;
|
||||
(* order is reversed by the unmarshaller, which is fine but we need to
|
||||
account for that when making equality assertions *)
|
||||
match Tcp.Options.unmarshal buf with
|
||||
| Error s -> Alcotest.fail s
|
||||
| Ok l -> Alcotest.(check @@ list options) "l" l opts
|
||||
|
||||
let test_unmarshal_random_data () =
|
||||
let random = Cstruct.create 64 in
|
||||
let iterations = 100 in
|
||||
Random.self_init ();
|
||||
let set_random pos =
|
||||
let num = Random.int32 Int32.max_int in
|
||||
Cstruct.BE.set_uint32 random pos num;
|
||||
in
|
||||
let rec check = function
|
||||
| n when n <= 0 -> ()
|
||||
| n ->
|
||||
List.iter set_random [0;4;8;12;16;20;24;28;32;36;40;44;48;52;56;60];
|
||||
Cstruct.hexdump random;
|
||||
(* acceptable outcomes: some list of options or the expected exception *)
|
||||
match Tcp.Options.unmarshal random with
|
||||
| Error _ -> (* Errors are OK, just finish *) ()
|
||||
| Ok l ->
|
||||
Tcp.Options.pps Format.std_formatter l;
|
||||
(* a really basic truth: the longest list we can have is 64 noops *)
|
||||
Alcotest.(check bool) "random" true (List.length l < 65);
|
||||
check (n - 1)
|
||||
in
|
||||
check iterations
|
||||
|
||||
let test_marshal_unknown () =
|
||||
let buf = Cstruct.create 10 in
|
||||
Cstruct.memset buf 255;
|
||||
let unknown = [ Tcp.Options.Unknown (64, " ") ] in (* overall, length 4 *)
|
||||
Alcotest.(check int) "4 bytes"
|
||||
4 (Tcp.Options.marshal buf unknown); (* should have written 4 bytes *)
|
||||
Cstruct.hexdump buf;
|
||||
(* option-kind *)
|
||||
Alcotest.(check int) "option kind" 64 (Cstruct.get_uint8 buf 0);
|
||||
(* option-length *)
|
||||
Alcotest.(check int)"option length" 4 (Cstruct.get_uint8 buf 1);
|
||||
(* data *)
|
||||
Alcotest.(check int) "data 1" 0x20 (Cstruct.get_uint8 buf 2);
|
||||
(* moar data *)
|
||||
Alcotest.(check int) "data 2" 0x20 (Cstruct.get_uint8 buf 3);
|
||||
(* unwritten region *)
|
||||
Alcotest.(check int) "canary" 255 (Cstruct.get_uint8 buf 4)
|
||||
|
||||
let test_options_marshal_padding () =
|
||||
let buf = Cstruct.create 8 in
|
||||
Cstruct.memset buf 255;
|
||||
let extract = Cstruct.get_uint8 buf in
|
||||
let needs_padding = [ Tcp.Options.SACK_ok ] in
|
||||
Alcotest.(check int) "padding" 4 (Tcp.Options.marshal buf needs_padding);
|
||||
Alcotest.(check int) "extract 0" 4 (extract 0);
|
||||
Alcotest.(check int) "extract 1" 2 (extract 1);
|
||||
(* should pad out the rest of the buffer with 0 *)
|
||||
Alcotest.(check int) "extract 2" 0 (extract 2);
|
||||
Alcotest.(check int) "extract 3" 0 (extract 3);
|
||||
(* but not keep padding into random memory *)
|
||||
Alcotest.(check int) "extract 4" 255 (extract 4)
|
||||
|
||||
let test_marshal_empty () =
|
||||
let buf = Cstruct.create 4 in
|
||||
Cstruct.memset buf 255;
|
||||
Alcotest.(check int) "0" 0 (Tcp.Options.marshal buf []);
|
||||
Alcotest.(check int) "255" 255 (Cstruct.get_uint8 buf 0)
|
||||
|
||||
let test_marshal_into_cstruct () =
|
||||
let options = [
|
||||
Tcp.Options.MSS 1460;
|
||||
Tcp.Options.SACK_ok;
|
||||
Tcp.Options.Window_size_shift 2
|
||||
] in
|
||||
(* MSS is 4 bytes, SACK_OK is 4 bytes, window_size_shift is 3, plus
|
||||
1 for padding *)
|
||||
let options_size = 12 in
|
||||
let buf = Cstruct.create (Tcp.Tcp_wire.sizeof_tcp + options_size) in
|
||||
Cstruct.memset buf 255;
|
||||
let src = Ipaddr.V4.of_string_exn "127.0.0.1" in
|
||||
let dst = Ipaddr.V4.of_string_exn "127.0.0.1" in
|
||||
let ipv4_header =
|
||||
{Ipv4_packet.src; dst; proto = 6; ttl = 64; id = 0 ; off = 0 ; options = Cstruct.create 0}
|
||||
in
|
||||
let payload = Cstruct.of_string "ab" in
|
||||
let pseudoheader =
|
||||
Ipv4_packet.Marshal.pseudoheader ~src ~dst ~proto:`TCP
|
||||
(Tcp.Tcp_wire.sizeof_tcp + options_size + Cstruct.length payload)
|
||||
in
|
||||
let packet =
|
||||
Tcp.Tcp_packet.{
|
||||
urg = false;
|
||||
ack = true;
|
||||
psh = false;
|
||||
rst = false;
|
||||
syn = true;
|
||||
fin = false;
|
||||
window = 0;
|
||||
options;
|
||||
sequence = Tcp.Sequence.of_int 255;
|
||||
ack_number = Tcp.Sequence.of_int 1024;
|
||||
src_port = 3000;
|
||||
dst_port = 6667;
|
||||
}
|
||||
in
|
||||
Tcp.Tcp_packet.Marshal.into_cstruct ~pseudoheader ~payload packet buf
|
||||
|> Alcotest.(check (result int string)) "correct size written"
|
||||
(Ok (Cstruct.length buf));
|
||||
let raw =Cstruct.concat [buf; payload] in
|
||||
Ipv4_packet.Unmarshal.verify_transport_checksum ~proto:`TCP ~ipv4_header
|
||||
~transport_packet:raw
|
||||
|> Alcotest.(check bool) "Checksum correct" true;
|
||||
Tcp.Tcp_packet.Unmarshal.of_cstruct raw
|
||||
|> Alcotest.(check (result (pair tcp_packet cstruct) string))
|
||||
"reload TCP packet" (Ok (packet, payload));
|
||||
let just_options = Cstruct.create options_size in
|
||||
let generated_options = Cstruct.shift buf Tcp.Tcp_wire.sizeof_tcp in
|
||||
Alcotest.(check int) "size of options buf" options_size @@
|
||||
Tcp.Options.marshal just_options options;
|
||||
(* expecting the result of Options.Marshal to be here *)
|
||||
Alcotest.check cstruct "marshalled options are as expected"
|
||||
just_options generated_options;
|
||||
(* Now try with make_cstruct *)
|
||||
let headers =
|
||||
Tcp.Tcp_packet.Marshal.make_cstruct ~pseudoheader ~payload packet
|
||||
in
|
||||
let raw =Cstruct.concat [headers; payload] in
|
||||
Ipv4_packet.Unmarshal.verify_transport_checksum ~proto:`TCP ~ipv4_header
|
||||
~transport_packet:raw
|
||||
|> Alcotest.(check bool) "Checksum correct" true
|
||||
|
||||
let test_marshal_without_padding () =
|
||||
let options = [ Tcp.Options.MSS 1460 ] in
|
||||
let options_size = 4 in (* MSS is 4 bytes *)
|
||||
let buf = Cstruct.create (Tcp.Tcp_wire.sizeof_tcp + options_size) in
|
||||
Cstruct.memset buf 255;
|
||||
let src = Ipaddr.V4.of_string_exn "127.0.0.1" in
|
||||
let dst = Ipaddr.V4.of_string_exn "127.0.0.1" in
|
||||
let ipv4_header =
|
||||
{Ipv4_packet.src; dst; proto = 6; ttl = 64; id = 0 ; off = 0 ; options = Cstruct.create 0}
|
||||
in
|
||||
let payload = Cstruct.of_string "\x02\x04\x05\xb4" in
|
||||
let pseudoheader =
|
||||
Ipv4_packet.Marshal.pseudoheader ~src ~dst ~proto:`TCP
|
||||
(Tcp.Tcp_wire.sizeof_tcp + options_size + Cstruct.length payload)
|
||||
in
|
||||
let packet =
|
||||
Tcp.Tcp_packet.{
|
||||
urg = false;
|
||||
ack = true;
|
||||
psh = false;
|
||||
rst = false;
|
||||
syn = true;
|
||||
fin = false;
|
||||
window = 0;
|
||||
options;
|
||||
sequence = Tcp.Sequence.of_int 255;
|
||||
ack_number = Tcp.Sequence.of_int 1024;
|
||||
src_port = 3000;
|
||||
dst_port = 6667;
|
||||
}
|
||||
in
|
||||
Tcp.Tcp_packet.Marshal.into_cstruct ~pseudoheader ~payload packet buf
|
||||
|> Alcotest.(check (result int string)) "correct size written"
|
||||
(Ok (Cstruct.length buf));
|
||||
let raw =Cstruct.concat [buf; payload] in
|
||||
Ipv4_packet.Unmarshal.verify_transport_checksum ~proto:`TCP ~ipv4_header
|
||||
~transport_packet:raw
|
||||
|> Alcotest.(check bool) "Checksum correct" true;
|
||||
Tcp.Tcp_packet.Unmarshal.of_cstruct raw
|
||||
|> Alcotest.(check (result (pair tcp_packet cstruct) string))
|
||||
"reload TCP packet" (Ok (packet, payload))
|
||||
|
||||
let suite = [
|
||||
"unmarshal broken mss", `Quick, test_unmarshal_bad_mss;
|
||||
"unmarshal option with bogus length", `Quick, test_unmarshal_bogus_length;
|
||||
"unmarshal option with zero length", `Quick, test_unmarshal_zero_length;
|
||||
"unmarshal simple cases", `Quick, test_unmarshal_simple_options;
|
||||
"unmarshal stops at eof", `Quick, test_unmarshal_stops_at_eof;
|
||||
"unmarshal non-broken tcp options", `Quick, test_unmarshal_ok_options;
|
||||
"unmarshalling random data returns", `Quick, test_unmarshal_random_data;
|
||||
"test marshalling into a cstruct", `Quick, test_marshal_into_cstruct;
|
||||
"test marshalling without padding", `Quick, test_marshal_without_padding;
|
||||
"test marshalling an unknown value", `Quick, test_marshal_unknown;
|
||||
"test options marshalling when padding is needed", `Quick,
|
||||
test_options_marshal_padding;
|
||||
"test marshalling the empty list", `Quick, test_marshal_empty;
|
||||
]
|
||||
|
||||
let suite =
|
||||
List.map (fun (n, s, f) -> n, s, (fun () -> Lwt.return (f ()))) suite
|
||||
Loading…
Add table
Add a link
Reference in a new issue