(* * Copyright (c) 2016 Pablo Polvorin * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) open Common open Lwt.Infix open Low_level (* Test scenarios *) (* Common sut: able to connect, connection not reset, no data received *) let sut_connects_and_remains_connected stack fail_callback = let conn = VNETIF_STACK.Stack.TCP.create_connection (VNETIF_STACK.Stack.tcp stack) in or_error "connect" conn (Ipaddr.V4 server_ip, 80) >>= fun flow -> (* We must remain blocked on read, connection shouldn't be terminated. * If after half second that remains true, assume test succeeds *) Lwt.pick [ (VNETIF_STACK.Stack.TCP.read flow >>= fail_result_not_expected fail_callback); Mirage_sleep.ns (Duration.of_ms 500) ] let blind_rst_on_syn_scenario = let fsm ip state ~src ~dst data = match state with | `WAIT_FOR_SYN -> let syn = Tcp_wire.get_syn data in if syn then ( let id = reply_id_from ~src ~dst data in (* This -blind- reset must be ignored because of invalid ack. *) WIRE.xmit ~ip id ~rst:true ~rx_ack:(ack_from_past data 1) ~seq:(Sequence.of_int32 0l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> (* The syn-ack must be received and connection established *) WIRE.xmit ~ip id ~syn:true ~rx_ack:(ack data) ~seq:(Sequence.of_int32 0l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_ACK) ) else Lwt.return (Fsm_error "Expected initial syn request") | `WAIT_FOR_ACK -> if Tcp_wire.get_ack data then ( Lwt.return Fsm_done ) else Lwt.return (Fsm_error "Expected final ack of three step dance") | `END -> Lwt.return (Fsm_error "nothing expected") in (`WAIT_FOR_SYN, fsm), sut_connects_and_remains_connected let connection_refused_scenario = let fsm ip state ~src ~dst data = match state with | `WAIT_FOR_SYN -> let syn = Tcp_wire.get_syn data in if syn then ( let id = reply_id_from ~src ~dst data in (* refused *) WIRE.xmit ~ip id ~rst:true ~rx_ack:(ack data) ~seq:(Sequence.of_int32 0l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return Fsm_done ) else Lwt.return (Fsm_error "Expected initial syn request") in let sut stack _fail = let conn = VNETIF_STACK.Stack.TCP.create_connection (VNETIF_STACK.Stack.tcp stack) in (* connection must be rejected *) expect_error `Refused "connect" conn (Ipaddr.V4 server_ip, 80) in (`WAIT_FOR_SYN, fsm), sut let blind_rst_on_established_scenario = let fsm ip state ~src ~dst data = match state with | `WAIT_FOR_SYN -> let syn = Tcp_wire.get_syn data in if syn then ( let id = reply_id_from ~src ~dst data in WIRE.xmit ~ip id ~syn:true ~rx_ack:(ack data) ~seq:(Sequence.of_int32 0l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_ACK) ) else Lwt.return (Fsm_error "Expected initial syn request") | `WAIT_FOR_ACK -> if Tcp_wire.get_ack data then ( (* This -blind- reset is acceptable, but don't exactly match the next sequence (we started at 0, this is 10). * Must trigger a challenge ack and not tear down the connection *) let id = reply_id_from ~src ~dst data in WIRE.xmit ~ip id ~rst:true ~rx_ack:None ~seq:(Sequence.of_int32 10l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_CHALLENGE) ) else Lwt.return (Fsm_error "Expected final ack of three way handshake") | `WAIT_FOR_CHALLENGE -> if (Tcp_wire.get_ack data) && (Tcp_wire.get_ack_number data = 1l) then Lwt.return Fsm_done else Lwt.return (Fsm_error "Challenge ack expected") in (`WAIT_FOR_SYN, fsm), sut_connects_and_remains_connected let rst_on_established_scenario = let fsm ip state ~src ~dst data = match state with | `WAIT_FOR_SYN -> let syn = Tcp_wire.get_syn data in if syn then ( let id = reply_id_from ~src ~dst data in WIRE.xmit ~ip id ~syn:true ~rx_ack:(ack data) ~seq:(Sequence.of_int32 0l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_ACK) ) else Lwt.return (Fsm_error "Expected initial syn request") | `WAIT_FOR_ACK -> if Tcp_wire.get_ack data then ( let id = reply_id_from ~src ~dst data in (* This reset is acceptable and exactly in sequence. Must trigger a reset on the other end *) WIRE.xmit ~ip id ~rst:true ~rx_ack:None ~seq:(Sequence.of_int32 1l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return Fsm_done ) else Lwt.return (Fsm_error "Expected final ack of three step dance") in let sut stack fail_callback = let conn = VNETIF_STACK.Stack.TCP.create_connection (VNETIF_STACK.Stack.tcp stack) in or_error "connect" conn (Ipaddr.V4 server_ip, 80) >>= fun flow -> VNETIF_STACK.Stack.TCP.read flow >>= function | Ok `Eof -> (* This is the expected when the other end resets *) Lwt.return_unit | other -> fail_result_not_expected fail_callback other in (`WAIT_FOR_SYN, fsm), sut let blind_syn_on_established_scenario = let fsm ip state ~src ~dst data = match state with | `WAIT_FOR_SYN -> let syn = Tcp_wire.get_syn data in if syn then ( let id = reply_id_from ~src ~dst data in WIRE.xmit ~ip id ~syn:true ~rx_ack:(ack data) ~seq:(Sequence.of_int32 0l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_ACK) ) else Lwt.return (Fsm_error "Expected initial syn request") | `WAIT_FOR_ACK -> if Tcp_wire.get_ack data then ( let id = reply_id_from ~src ~dst data in (* This -blind- syn should trigger a challenge ack and not tear down the connection *) WIRE.xmit ~ip id ~syn:true ~rx_ack:None ~seq:(Sequence.of_int32 10l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_CHALLENGE) ) else Lwt.return (Fsm_error "Expected final ack of three step dance") | `WAIT_FOR_CHALLENGE -> if (Tcp_wire.get_ack data) && (Tcp_wire.get_ack_number data = 1l) then ( Lwt.return Fsm_done ) else Lwt.return (Fsm_error "Challenge ack expected") in (`WAIT_FOR_SYN, fsm), sut_connects_and_remains_connected let blind_data_injection_scenario = let page = Cstruct.create 512 in let fsm ip state ~src ~dst data = match state with | `WAIT_FOR_SYN -> let syn = Tcp_wire.get_syn data in if syn then ( let id = reply_id_from ~src ~dst data in WIRE.xmit ~ip id ~syn:true ~rx_ack:(ack data) ~seq:(Sequence.of_int32 1000000l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_ACK) ) else Lwt.return (Fsm_error "Expected initial syn request") | `WAIT_FOR_ACK -> if Tcp_wire.get_ack data then ( let id = reply_id_from ~src ~dst data in (* This -blind- data should trigger a challenge ack and not tear down the connection *) let invalid_ack = ack_from_past data (window +100) in WIRE.xmit ~ip id ~rx_ack:invalid_ack ~seq:(Sequence.of_int32 1000001l) ~window ~options page >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_CHALLENGE) ) else Lwt.return (Fsm_error "Expected final ack of three step dance") | `WAIT_FOR_CHALLENGE -> if (Tcp_wire.get_ack data) && (Tcp_wire.get_ack_number data = 1000001l) then Lwt.return Fsm_done else Lwt.return (Fsm_error "Challenge ack expected") in (`WAIT_FOR_SYN, fsm), sut_connects_and_remains_connected let data_repeated_ack_scenario = (* This is the just data transmission with ack in the past but within the acceptable window *) let page = Cstruct.create 512 in let fsm ip state ~src ~dst data = match state with | `WAIT_FOR_SYN -> let syn = Tcp_wire.get_syn data in if syn then ( let id = reply_id_from ~src ~dst data in WIRE.xmit ~ip id ~syn:true ~rx_ack:(ack data) ~seq:(Sequence.of_int32 1000000l) ~window ~options (Cstruct.create 0) >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_ACK) ) else Lwt.return (Fsm_error "Expected initial syn request") | `WAIT_FOR_ACK -> if Tcp_wire.get_ack data then ( let id = reply_id_from ~src ~dst data in (* Ack is old but within the acceptable window. *) let valid_ack = ack_from_past data (window -100) in WIRE.xmit ~ip id ~rx_ack:valid_ack ~seq:(Sequence.of_int32 1000001l) ~window ~options page >|= Result.get_ok >>= fun () -> Lwt.return (Fsm_next `WAIT_FOR_DATA_ACK) ) else Lwt.return (Fsm_error "Expected final ack of three step dance") | `WAIT_FOR_DATA_ACK -> if (Tcp_wire.get_ack data) && (Tcp_wire.get_ack_number data = Int32.(add 1000001l (of_int (Cstruct.length page)))) then Lwt.return Fsm_done else Lwt.return (Fsm_error "Ack for data expected") in let sut stack fail_callback = let conn = VNETIF_STACK.Stack.TCP.create_connection (VNETIF_STACK.Stack.tcp stack) in or_error "connect" conn (Ipaddr.V4 server_ip, 80) >>= fun flow -> (* We should receive the data *) VNETIF_STACK.Stack.TCP.read flow >>= function | Ok _ -> Lwt.return_unit | other -> fail_result_not_expected fail_callback other in (`WAIT_FOR_SYN, fsm), sut let run_test pcap_file ((initial_state, fsm), sut) () = let backend = VNETIF_STACK.create_backend () in VNETIF_STACK.record_pcap backend pcap_file (run backend (initial_state, fsm) sut) let suite = [ "blind rst to syn_sent", `Quick, run_test "tcp_blind_rst_on_syn.pcap" blind_rst_on_syn_scenario ; "connection refused", `Quick, run_test "tcp_connection_refused.pcap" connection_refused_scenario; "blind rst on established", `Quick, run_test "tcp_blind_rst_on_established.pcap" blind_rst_on_established_scenario; "rst on established", `Quick, run_test "tcp_rst_on_established.pcap" rst_on_established_scenario; "blind syn on established", `Quick, run_test "tcp_blind_syn_on_established.pcap" blind_syn_on_established_scenario; "blind data injection", `Quick, run_test "tcp_blind_data_injection.pcap" blind_data_injection_scenario; "data repeated ack", `Quick, run_test "tcp_data_repeated_ack.pcap" data_repeated_ack_scenario; ]