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