open Common module B = Vnetif_backends.Basic module V = Vnetif.Make(B) module E = Ethernet.Make(V) module Ipv6 = Ipv6.Make(V)(E) module Udp = Udp.Make(Ipv6) open Lwt.Infix let ip = let module M = struct type t = Ipaddr.V6.t let pp = Ipaddr.V6.pp let equal p q = (Ipaddr.V6.compare p q) = 0 end in (module M : Alcotest.TESTABLE with type t = M.t) type stack = { backend : B.t; netif : V.t; ethif : E.t; ip : Ipv6.t; udp : Udp.t } let get_stack backend address = let cidr = Ipaddr.V6.Prefix.make 64 address in V.connect backend >>= fun netif -> E.connect netif >>= fun ethif -> Ipv6.connect ~cidr netif ethif >>= fun ip -> Udp.connect ip >>= fun udp -> Lwt.return { backend; netif; ethif; ip; udp } let noop = fun ~src:_ ~dst:_ _ -> Lwt.return_unit let listen ?(tcp = noop) ?(udp = noop) ?(default = noop) stack = V.listen stack.netif ~header_size:Ethernet.Packet.sizeof_ethernet ( E.input stack.ethif ~arpv4:(fun _ -> Lwt.return_unit) ~ipv4:(fun _ -> Lwt.return_unit) ~ipv6:( Ipv6.input stack.ip ~tcp:tcp ~udp:udp ~default:(fun ~proto:_ -> default))) >>= fun _ -> Lwt.return_unit let udp_message = Cstruct.of_string "hello on UDP over IPv6" let check_for_one_udp_packet on_received_one ~src ~dst buf = (match Udp_packet.Unmarshal.of_cstruct buf with | Ok (_, payload) -> Alcotest.(check ip) "sender address" (Ipaddr.V6.of_string_exn "fc00::23") src; Alcotest.(check ip) "receiver address" (Ipaddr.V6.of_string_exn "fc00::45") dst; Alcotest.(check cstruct) "payload is correct" udp_message payload | Error m -> Alcotest.fail m); (try Lwt.wakeup_later on_received_one () with _ -> () (* the first succeeds, the rest raise *)); Lwt.return_unit let send_forever sender receiver_address udp_message = let rec loop () = Udp.write sender.udp ~dst:receiver_address ~dst_port:1234 udp_message >|= Result.get_ok >>= fun () -> Mirage_sleep.ns (Duration.of_ms 50) >>= fun () -> loop () in loop () let pass_udp_traffic () = let sender_address = Ipaddr.V6.of_string_exn "fc00::23" in let receiver_address = Ipaddr.V6.of_string_exn "fc00::45" in let backend = B.create () in get_stack backend sender_address >>= fun sender -> get_stack backend receiver_address >>= fun receiver -> let received_one, on_received_one = Lwt.task () in Lwt.pick [ listen receiver ~udp:(check_for_one_udp_packet on_received_one); listen sender; send_forever sender receiver_address udp_message; received_one; (* stop on the first packet *) Mirage_sleep.ns (Duration.of_ms 3000) >>= fun () -> Alcotest.fail "UDP packet should have been received"; ] let create_ethernet backend = V.connect backend >>= fun netif -> E.connect netif >|= fun ethif -> (fun ipv6 -> V.listen netif ~header_size:Ethernet.Packet.sizeof_ethernet (E.input ethif ~arpv4:(fun _ -> Lwt.return_unit) ~ipv4:(fun _ -> Lwt.return_unit) ~ipv6) >|= fun _ -> ()), (fun dst ?size f -> E.write ethif dst `IPv6 ?size f), E.mac ethif let solicited_node_prefix = Ipaddr.V6.(Prefix.make 104 (of_int16 (0xff02, 0, 0, 0, 0, 1, 0xff00, 0))) let dad_na_is_sent () = let address = Ipaddr.V6.of_string_exn "fc00::23" in let backend = B.create () in get_stack backend address >>= fun stack -> create_ethernet backend >>= fun (listen_raw, write_raw, _) -> let received_one, on_received_one = Lwt.task () in let nd_size = Ipv6_wire.sizeof_ipv6 + Ipv6_wire.Ns.sizeof_ns in let nd buf = Ipv6_wire.set_version_flow buf 0x60000000l; (* IPv6 *) Ipv6_wire.set_len buf Ipv6_wire.Ns.sizeof_ns; Ipaddr_cstruct.V6.write_cstruct_exn Ipaddr.V6.unspecified (Cstruct.shift buf 8); Ipaddr_cstruct.V6.write_cstruct_exn (Ipaddr.V6.Prefix.network_address solicited_node_prefix address) (Cstruct.shift buf 24); Ipv6_wire.set_hlim buf 255; Ipv6_wire.set_nhdr buf (Ipv6_wire.protocol_to_int `ICMP); let hdr, icmpbuf = Cstruct.split buf Ipv6_wire.sizeof_ipv6 in Ipv6_wire.set_ty icmpbuf 135; (* NS *) Ipv6_wire.set_code icmpbuf 0; Ipv6_wire.Ns.set_reserved icmpbuf 0l; Ipaddr_cstruct.V6.write_cstruct_exn address (Cstruct.shift icmpbuf 8); Ipv6_wire.Icmpv6.set_checksum icmpbuf 0; Ipv6_wire.Icmpv6.set_checksum icmpbuf @@ Ndpv6.checksum hdr [icmpbuf]; nd_size and is_na buf = let icmpbuf = Cstruct.shift buf Ipv6_wire.sizeof_ipv6 in Ipv6_wire.get_version_flow buf = 0x60000000l && (* IPv6 *) Ipaddr.V6.compare (Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.shift buf 8)) address = 0 && Ipaddr.V6.compare (Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.shift buf 24)) Ipaddr.V6.link_nodes = 0 && Ipv6_wire.get_hlim buf = 255 && Ipv6_wire.get_nhdr buf = Ipv6_wire.protocol_to_int `ICMP && Ipv6_wire.get_ty icmpbuf = 136 && Ipv6_wire.get_code icmpbuf = 0 && Ipaddr.V6.compare (Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.shift icmpbuf 8)) address = 0 in Lwt.pick [ listen stack; listen_raw (fun buf -> if is_na buf then Lwt.wakeup_later on_received_one (); Lwt.return_unit); (write_raw (E.mac stack.ethif) ~size:nd_size nd >|= fun _ -> ()); received_one; (Mirage_sleep.ns (Duration.of_ms 1000) >>= fun () -> Alcotest.fail "NA packet should have been received") ] let multicast_mac = let pbuf = Cstruct.create 6 in Cstruct.BE.set_uint16 pbuf 0 0x3333; fun ip -> let _, _, _, n = Ipaddr.V6.to_int32 ip in Cstruct.BE.set_uint32 pbuf 2 n; Macaddr_cstruct.of_cstruct_exn pbuf let dad_na_is_received () = let address = Ipaddr.V6.of_string_exn "fc00::23" in let backend = B.create () in create_ethernet backend >>= fun (listen_raw, write_raw, mac) -> let na_size = Ipv6_wire.sizeof_ipv6 + Ipv6_wire.Na.sizeof_na + Ipv6_wire.Llopt.sizeof_llopt in let is_ns buf = let icmpbuf = Cstruct.shift buf Ipv6_wire.sizeof_ipv6 in if Ipv6_wire.get_version_flow buf = 0x60000000l && (* IPv6 *) Ipaddr.V6.compare (Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.shift buf 8)) Ipaddr.V6.unspecified = 0 && Ipaddr.V6.Prefix.mem (Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.shift buf 24)) solicited_node_prefix && Ipv6_wire.get_hlim buf = 255 && Ipv6_wire.get_nhdr buf = Ipv6_wire.protocol_to_int `ICMP && Ipv6_wire.get_ty icmpbuf = 135 && Ipv6_wire.get_code icmpbuf = 0 then Some (Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.shift icmpbuf 8)) else None in let na addr buf = Ipv6_wire.set_version_flow buf 0x60000000l; (* IPv6 *) Ipv6_wire.set_len buf (Ipv6_wire.Na.sizeof_na + Ipv6_wire.Llopt.sizeof_llopt); Ipaddr_cstruct.V6.write_cstruct_exn addr (Cstruct.shift buf 8); Ipaddr_cstruct.V6.write_cstruct_exn Ipaddr.V6.link_nodes (Cstruct.shift buf 24); Ipv6_wire.set_hlim buf 255; Ipv6_wire.set_nhdr buf (Ipv6_wire.protocol_to_int `ICMP); let hdr, icmpbuf = Cstruct.split buf Ipv6_wire.sizeof_ipv6 in Ipv6_wire.set_ty icmpbuf 136; (* NA *) Ipv6_wire.set_code icmpbuf 0; Ipv6_wire.Na.set_reserved icmpbuf 0x20000000l; Ipaddr_cstruct.V6.write_cstruct_exn addr (Cstruct.shift icmpbuf 8); let optbuf = Cstruct.shift icmpbuf Ipv6_wire.Na.sizeof_na in Ipv6_wire.set_ty optbuf 2; Ipv6_wire.Llopt.set_len optbuf 1; Macaddr_cstruct.write_cstruct_exn mac (Cstruct.shift optbuf 2); Ipv6_wire.Icmpv6.set_checksum icmpbuf 0; Ipv6_wire.Icmpv6.set_checksum icmpbuf @@ Ndpv6.checksum hdr [icmpbuf]; na_size in Lwt.pick [ (listen_raw (fun buf -> match is_ns buf with | None -> Lwt.return_unit | Some addr -> let dst = multicast_mac Ipaddr.V6.link_nodes in write_raw dst ~size:na_size (na addr) >|= fun _ -> ())); (Lwt.catch (fun () -> get_stack backend address >|= fun _ -> Error ()) (fun _ -> Lwt.return (Ok ())) >|= function | Ok () -> () | Error () -> Alcotest.fail "Expected stack initialization failure"); (Mirage_sleep.ns (Duration.of_ms 5000) >>= fun () -> Alcotest.fail "stack initialization should have failed") ] let suite = [ "Send a UDP packet from one IPV6 stack and check it is received by another", `Quick, pass_udp_traffic; "NA is sent when a ND is received", `Quick, dad_na_is_sent; "NA is received, stack fails to initialise", `Quick, dad_na_is_received; ]