From 2eac90904b19455e2fae07c53f1707f17f76bc84 Mon Sep 17 00:00:00 2001 From: Hannes Mehnert Date: Wed, 2 Sep 2020 22:58:31 +0200 Subject: [PATCH 1/6] IPv6.connect: only return once any IP address is configured This reflects the behaviour of DHCP in the IPv4 stack: we have to temporarily start the Netif.listen function to potentially receive neighbour advertisements (and router advertisements etc.), and once an IP address moves into the PREFERRED state, we signal completion to our caller. This avoids busy-waiting polling loops in the tests (and all other user code that first needs to ensure to be able to send out data). It is not yet clear to me whether this is a sensible and useful solution (esp. in a dual stack with IPv4 configured by DHCP setup) for the long term, but in the medium term it is good enough: a IPv6 stack is only useful for transport layers (UDP, TCP) if we can send and receive datagrams over it. --- src/ipv6/dune | 4 ++-- src/ipv6/ipv6.ml | 35 +++++++++++++++++++++++++++-------- src/ipv6/ipv6.mli | 5 +++-- test/test_ipv6.ml | 14 ++++---------- test/vnetif_common.ml | 16 +++------------- 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/ipv6/dune b/src/ipv6/dune index 9aa0cb4ec..28b467d80 100644 --- a/src/ipv6/dune +++ b/src/ipv6/dune @@ -1,8 +1,8 @@ (library (name tcpip_ipv6) (public_name tcpip.ipv6) - (libraries logs mirage-protocols mirage-time macaddr-cstruct + (libraries logs mirage-protocols mirage-time mirage-net macaddr-cstruct mirage-clock duration ipaddr cstruct rresult mirage-random tcpip - randomconv) + randomconv ethernet) (preprocess (pps ppx_cstruct)) (wrapped false)) diff --git a/src/ipv6/ipv6.ml b/src/ipv6/ipv6.ml index 800e864c5..9545a6ddd 100644 --- a/src/ipv6/ipv6.ml +++ b/src/ipv6/ipv6.ml @@ -21,7 +21,8 @@ module I = Ipaddr open Lwt.Infix -module Make (E : Mirage_protocols.ETHERNET) +module Make (N : Mirage_net.S) + (E : Mirage_protocols.ETHERNET) (R : Mirage_random.S) (T : Mirage_time.S) (C : Mirage_clock.MCLOCK) = struct @@ -45,15 +46,20 @@ module Make (E : Mirage_protocols.ETHERNET) let output_ign t a = output t a >|= fun _ -> () - let start_ticking t = - let rec loop () = + let start_ticking t u = + let rec loop u = let now = C.elapsed_ns () in let ctx, outs = Ndpv6.tick ~now t.ctx in t.ctx <- ctx; + let u = match u, Ndpv6.get_ip t.ctx with + | None, _ | _, [] -> u + | Some u, _ -> Lwt.wakeup_later u (); None + in Lwt_list.iter_s (output_ign t) outs (* MCP: replace with propagation *) >>= fun () -> - T.sleep_ns (Duration.of_sec 1) >>= loop + T.sleep_ns (Duration.of_sec 1) >>= fun () -> + loop u in - loop () + loop (Some u) let mtu t = E.mtu t.ethif - Ipv6_wire.sizeof_ipv6 @@ -143,7 +149,7 @@ module Make (E : Mirage_protocols.ETHERNET) | Some x -> f x >>= g | None -> g () - let connect ?ip ?netmask ?gateways ethif = + let connect ?ip ?netmask ?gateways netif ethif = Log.info (fun f -> f "IP6: Starting"); let now = C.elapsed_ns () in let ctx, outs = Ndpv6.local ~now ~random:R.generate (E.mac ethif) in @@ -153,7 +159,20 @@ module Make (E : Mirage_protocols.ETHERNET) (ip, Lwt_list.iter_s (set_ip t)) >>=? fun () -> (netmask, Lwt_list.iter_s (set_ip_netmask t)) >>=? fun () -> (gateways, set_ip_gateways t) >>=? fun () -> - Lwt.async (fun () -> start_ticking t); - Lwt.return t + let task, u = Lwt.task () in + Lwt.async (fun () -> start_ticking t u); + (* call listen until we're good in respect to DAD *) + let ethif_listener = + let noop ~src:_ ~dst:_ _ = Lwt.return_unit in + E.input ethif + ~arpv4:(fun _ -> Lwt.return_unit) + ~ipv4:(fun _ -> Lwt.return_unit) + ~ipv6:(input t ~tcp:noop ~udp:noop ~default:(fun ~proto:_ -> noop)) + in + Lwt.pick [ + (N.listen netif ~header_size:Ethernet_wire.sizeof_ethernet ethif_listener >|= fun _ -> ()) ; + task + ] >|= fun () -> + t end diff --git a/src/ipv6/ipv6.mli b/src/ipv6/ipv6.mli index f548a0387..0159024e4 100644 --- a/src/ipv6/ipv6.mli +++ b/src/ipv6/ipv6.mli @@ -14,7 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) -module Make (E : Mirage_protocols.ETHERNET) +module Make (N : Mirage_net.S) + (E : Mirage_protocols.ETHERNET) (R : Mirage_random.S) (T : Mirage_time.S) (Clock : Mirage_clock.MCLOCK) : sig @@ -23,5 +24,5 @@ module Make (E : Mirage_protocols.ETHERNET) ?ip:Ipaddr.V6.t list -> ?netmask:Ipaddr.V6.Prefix.t list -> ?gateways:Ipaddr.V6.t list -> - E.t -> t Lwt.t + N.t -> E.t -> t Lwt.t end diff --git a/test/test_ipv6.ml b/test/test_ipv6.ml index bdcd862f1..c078e06ab 100644 --- a/test/test_ipv6.ml +++ b/test/test_ipv6.ml @@ -4,7 +4,7 @@ module B = Vnetif_backends.Basic module V = Vnetif.Make(B) module E = Ethernet.Make(V) -module Ipv6 = Ipv6.Make(E)(Mirage_random_test)(Time)(Mclock) +module Ipv6 = Ipv6.Make(V)(E)(Mirage_random_test)(Time)(Mclock) module Udp = Udp.Make(Ipv6)(Mirage_random_test) open Lwt.Infix @@ -30,7 +30,7 @@ let get_stack backend address = let gateways = [] in V.connect backend >>= fun netif -> E.connect netif >>= fun ethif -> - Ipv6.connect ~ip ~netmask ~gateways ethif >>= fun ip -> + Ipv6.connect ~ip ~netmask ~gateways netif ethif >>= fun ip -> Udp.connect ip >>= fun udp -> Lwt.return { backend; netif; ethif; ip; udp } @@ -61,14 +61,8 @@ let check_for_one_udp_packet on_received_one ~src ~dst buf = let send_forever sender receiver_address udp_message = let rec loop () = - (* Check that we have an IP before sending *) - if List.length (Ipv6.get_ip sender.ip) >= 1 then - begin - Udp.write sender.udp ~dst:receiver_address ~dst_port:1234 udp_message - >|= Rresult.R.get_ok - end else - Lwt.return_unit - >>= fun () -> + Udp.write sender.udp ~dst:receiver_address ~dst_port:1234 udp_message + >|= Rresult.R.get_ok >>= fun () -> Time.sleep_ns (Duration.of_ms 50) >>= fun () -> loop () in loop () diff --git a/test/vnetif_common.ml b/test/vnetif_common.ml index 7c7145f43..fa2046bee 100644 --- a/test/vnetif_common.ml +++ b/test/vnetif_common.ml @@ -76,7 +76,7 @@ struct module U4 = Udp.Make(Ip4)(Mirage_random_test) module T4 = Tcp.Flow.Make(Ip4)(Time)(Clock)(Mirage_random_test) - module Ip6 = Ipv6.Make(E)(Mirage_random_test)(Time)(Clock) + module Ip6 = Ipv6.Make(V)(E)(Mirage_random_test)(Time)(Clock) module U6 = Udp.Make(Ip6)(Mirage_random_test) module T6 = Tcp.Flow.Make(Ip6)(Time)(Clock)(Mirage_random_test) @@ -89,15 +89,6 @@ struct let create_backend () = B.create () - let rec wait_for_ipv6 t () = - (* Wait for IP to be valid *) - if List.length (Ip6.get_ip (Stackv6.ip t)) >= 1 then - begin - Lwt.return t - end else - Time.sleep_ns (Duration.of_ms 50) >>= fun () -> - wait_for_ipv6 t () - let create_stack ?mtu ~cidr ?gateway backend = let size_limit = match mtu with None -> None | Some x -> Some x in V.connect ?size_limit backend >>= fun netif -> @@ -113,11 +104,10 @@ struct let size_limit = match mtu with None -> None | Some x -> Some x in V.connect ?size_limit backend >>= fun netif -> E.connect netif >>= fun ethif -> - Ip6.connect ?ip ?netmask ?gateways ethif >>= fun ipv6 -> + Ip6.connect ?ip ?netmask ?gateways netif ethif >>= fun ipv6 -> U6.connect ipv6 >>= fun udpv6 -> T6.connect ipv6 >>= fun tcpv6 -> - Stackv6.connect netif ethif ipv6 udpv6 tcpv6 >>= fun s -> - wait_for_ipv6 s () + Stackv6.connect netif ethif ipv6 udpv6 tcpv6 let create_backend_listener backend listenf = match (B.register backend) with From ceeb9cbd195bf675f3e78865fc87773eea4b5f98 Mon Sep 17 00:00:00 2001 From: Hannes Mehnert Date: Wed, 9 Sep 2020 12:28:28 +0200 Subject: [PATCH 2/6] IPv6: fix implementation of DAD (Duplicate Address Detection) protocol - RFC 4861 (4.3) requires ND to _not_ contain the link-level address if source=unspecified - RFC 4861 (4.4) requires NA to not have flag S set if multicast or unsolicited unicast advertisements Ipv6.connect: install a timeout (3 seconds) -- if no IP address is configured after this timeout, fail (Lwt.fail) --- src/ipv6/ipv6.ml | 13 ++++++++++--- src/ipv6/ndpv6.ml | 38 +++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/ipv6/ipv6.ml b/src/ipv6/ipv6.ml index 9545a6ddd..292622048 100644 --- a/src/ipv6/ipv6.ml +++ b/src/ipv6/ipv6.ml @@ -169,10 +169,17 @@ module Make (N : Mirage_net.S) ~ipv4:(fun _ -> Lwt.return_unit) ~ipv6:(input t ~tcp:noop ~udp:noop ~default:(fun ~proto:_ -> noop)) in + let timeout = T.sleep_ns (Duration.of_sec 3) in Lwt.pick [ (N.listen netif ~header_size:Ethernet_wire.sizeof_ethernet ethif_listener >|= fun _ -> ()) ; - task - ] >|= fun () -> - t + task ; + timeout + ] >>= fun () -> + match get_ip t with + | [] -> Lwt.fail_with "IP6 not started, couldn't assign IP" + | ips -> + Log.info (fun f -> f "IP6: Started with %a" + Fmt.(list ~sep:(unit ",@ ") Ipaddr.V6.pp) ips); + Lwt.return t end diff --git a/src/ipv6/ndpv6.ml b/src/ipv6/ndpv6.ml index f712646b9..908bf6f12 100644 --- a/src/ipv6/ndpv6.ml +++ b/src/ipv6/ndpv6.ml @@ -169,17 +169,19 @@ module Allocate = struct in (size', fill) - let ns ~mac ~src ~dst ~tgt = - let size = Ipv6_wire.sizeof_ns + Ipv6_wire.sizeof_llopt in + let ns ~specified ~mac ~src ~dst ~tgt = + let size = Ipv6_wire.sizeof_ns + if specified then Ipv6_wire.sizeof_llopt else 0 in let fillf hdr icmpbuf = let optbuf = Cstruct.shift icmpbuf Ipv6_wire.sizeof_ns in Ipv6_wire.set_ns_ty icmpbuf 135; (* NS *) Ipv6_wire.set_ns_code icmpbuf 0; Ipv6_wire.set_ns_reserved icmpbuf 0l; ipaddr_to_cstruct_raw tgt (Ipv6_wire.get_ns_target icmpbuf) 0; - Ipv6_wire.set_llopt_ty optbuf 1; - Ipv6_wire.set_llopt_len optbuf 1; - macaddr_to_cstruct_raw mac optbuf 2; + if specified then begin + Ipv6_wire.set_llopt_ty optbuf 1; + Ipv6_wire.set_llopt_len optbuf 1; + macaddr_to_cstruct_raw mac optbuf 2; + end; Ipv6_wire.set_icmpv6_csum icmpbuf 0; Ipv6_wire.set_icmpv6_csum icmpbuf @@ checksum hdr [ icmpbuf ]; size @@ -1047,13 +1049,13 @@ let next_hop ctx ip = let rec process_actions ~now ctx actions = let aux ctx = function | SendNS (unspec, dst, tgt) -> - let src = match unspec with - | `Unspecified -> Ipaddr.unspecified - | `Specified -> AddressList.select_source ctx.address_list ~dst + let src, specified = match unspec with + | `Unspecified -> Ipaddr.unspecified, false + | `Specified -> AddressList.select_source ctx.address_list ~dst, true in Log.debug (fun f -> f "ND6: Sending NS src=%a dst=%a tgt=%a" Ipaddr.pp src Ipaddr.pp dst Ipaddr.pp tgt); - let size, fillf = Allocate.ns ~mac:ctx.mac ~src ~dst ~tgt in + let size, fillf = Allocate.ns ~specified ~mac:ctx.mac ~src ~dst ~tgt in send' ~now ctx dst size fillf | SendNA (src, dst, tgt, sol) -> let sol = match sol with `Solicited -> true | `Unsolicited -> false in @@ -1210,12 +1212,18 @@ let handle_ns ~now:_ ctx ~src ~dst ns = | None -> ctx, [] in - if AddressList.is_my_addr ctx.address_list ns.ns_target then - let src = ns.ns_target and dst = src in -(* (\* Log.debug (fun f -> f "Sending NA to %a from %a with target address %a" *\) *) -(* (\* Ipaddr.pp dst Ipaddr.pp src Ipaddr.pp target); *\) *) - ctx, SendNA (src, dst, ns.ns_target, `Solicited) :: actions - else + if AddressList.is_my_addr ctx.address_list ns.ns_target then begin + let src = ns.ns_target + and dst, sol = + if Ipaddr.(compare src unspecified = 0) then + Ipaddr.link_nodes, `Unsolicited + else + src, `Solicited + in + (* Log.debug (fun f -> f "Sending NA to %a from %a with target address %a" + Ipaddr.pp dst Ipaddr.pp src Ipaddr.pp ns.ns_target); *) + ctx, SendNA (src, dst, ns.ns_target, sol) :: actions + end else ctx, actions let handle_na ~now ctx ~src ~dst na = From bc708f94aa4e329bb2c6e72426a29fa3f053fe77 Mon Sep 17 00:00:00 2001 From: Hannes Mehnert Date: Thu, 10 Sep 2020 22:14:54 +0200 Subject: [PATCH 3/6] ipv6: packet check buffer length against advertised length to avoid out of bound access --- src/ipv6/ndpv6.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipv6/ndpv6.ml b/src/ipv6/ndpv6.ml index 908bf6f12..d136e8a84 100644 --- a/src/ipv6/ndpv6.ml +++ b/src/ipv6/ndpv6.ml @@ -995,7 +995,7 @@ module Parser = struct loop (poff+2) let packet is_my_addr buf = - if Cstruct.len buf < Ipv6_wire.sizeof_ipv6 then begin + if Cstruct.len buf < Ipv6_wire.sizeof_ipv6 || Cstruct.len buf < Ipv6_wire.sizeof_ipv6 + Ipv6_wire.get_ipv6_len buf then begin Log.debug (fun m -> m "short IPv6 packet received, dropping"); Drop end else if Int32.logand (Ipv6_wire.get_ipv6_version_flow buf) 0xF0000000l <> 0x60000000l then begin From 0ab9fb16f9da1e492f1c8da4e26264cd0624bcd7 Mon Sep 17 00:00:00 2001 From: Hannes Mehnert Date: Thu, 10 Sep 2020 22:16:21 +0200 Subject: [PATCH 4/6] test_ipv6: test that a neighbor advertisement is sent for our IP (DAD) --- tcpip.opam | 1 + test/dune | 2 +- test/test_ipv6.ml | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/tcpip.opam b/tcpip.opam index 9fbe2cb98..dc12aea9b 100644 --- a/tcpip.opam +++ b/tcpip.opam @@ -50,6 +50,7 @@ depends: [ "mirage-clock-unix" {with-test & >= "3.0.0"} "mirage-random-test" {with-test & >= "0.1.0"} "arp-mirage" {with-test & >= "2.0.0"} + "ipaddr-cstruct" {with-test} "lru" {>= "0.3.0"} ] synopsis: "OCaml TCP/IP networking stack, used in MirageOS" diff --git a/test/dune b/test/dune index 8a1ee2b3b..6656fe37f 100644 --- a/test/dune +++ b/test/dune @@ -5,7 +5,7 @@ pcap-format duration mirage-random rresult mirage-protocols mirage-stack arp arp-mirage ethernet tcpip.ipv4 tcpip.tcp tcpip.udp tcpip.stack-direct tcpip.icmpv4 tcpip.udpv4-socket tcpip.tcpv4-socket - tcpip.icmpv4-socket tcpip.stack-socket tcpip.ipv6)) + tcpip.icmpv4-socket tcpip.stack-socket tcpip.ipv6 ipaddr-cstruct)) (alias (name runtest) diff --git a/test/test_ipv6.ml b/test/test_ipv6.ml index c078e06ab..d9a4e6293 100644 --- a/test/test_ipv6.ml +++ b/test/test_ipv6.ml @@ -83,6 +83,69 @@ let pass_udp_traffic () = 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_wire.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) + +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.sizeof_ns in + let nd buf = + Ipv6_wire.set_ipv6_version_flow buf 0x60000000l; (* IPv6 *) + Ipv6_wire.set_ipv6_len buf Ipv6_wire.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_ipv6_hlim buf 255; + Ipv6_wire.set_ipv6_nhdr buf (Ipv6_wire.protocol_to_int `ICMP); + let icmpbuf = Cstruct.shift buf Ipv6_wire.sizeof_ipv6 in + Ipv6_wire.set_ns_ty icmpbuf 135; (* NS *) + Ipv6_wire.set_ns_code icmpbuf 0; + Ipv6_wire.set_ns_reserved icmpbuf 0l; + Ipaddr_cstruct.V6.write_cstruct_exn address (Cstruct.shift icmpbuf 6); + Ipv6_wire.set_icmpv6_csum icmpbuf 0; + Ipv6_wire.set_icmpv6_csum icmpbuf @@ Ndpv6.checksum buf []; + nd_size + and is_na buf = + let icmpbuf = Cstruct.shift buf Ipv6_wire.sizeof_ipv6 in + Ipv6_wire.get_ipv6_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_ipv6_hlim buf = 255 && + Ipv6_wire.get_ipv6_nhdr buf = Ipv6_wire.protocol_to_int `ICMP && + Ipv6_wire.get_ns_ty icmpbuf = 136 && + Ipv6_wire.get_ns_code icmpbuf = 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; + (Time.sleep_ns (Duration.of_ms 1000) >>= fun () -> + Alcotest.fail "NA packet should have been received") + ] + 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; ] From 18ed59df114309433c26208765737d88155c7f18 Mon Sep 17 00:00:00 2001 From: Hannes Mehnert Date: Thu, 10 Sep 2020 23:09:17 +0200 Subject: [PATCH 5/6] ipv6: send NS only when already listening to network interface (to avoid races) --- src/ipv6/ipv6.ml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ipv6/ipv6.ml b/src/ipv6/ipv6.ml index 292622048..c0b90ef11 100644 --- a/src/ipv6/ipv6.ml +++ b/src/ipv6/ipv6.ml @@ -154,11 +154,6 @@ module Make (N : Mirage_net.S) let now = C.elapsed_ns () in let ctx, outs = Ndpv6.local ~now ~random:R.generate (E.mac ethif) in let t = {ctx; ethif} in - (* MCP: replace this error swallowing with proper propagation *) - Lwt_list.iter_s (output_ign t) outs >>= fun () -> - (ip, Lwt_list.iter_s (set_ip t)) >>=? fun () -> - (netmask, Lwt_list.iter_s (set_ip_netmask t)) >>=? fun () -> - (gateways, set_ip_gateways t) >>=? fun () -> let task, u = Lwt.task () in Lwt.async (fun () -> start_ticking t u); (* call listen until we're good in respect to DAD *) @@ -171,8 +166,13 @@ module Make (N : Mirage_net.S) in let timeout = T.sleep_ns (Duration.of_sec 3) in Lwt.pick [ + (* MCP: replace this error swallowing with proper propagation *) + (Lwt_list.iter_s (output_ign t) outs >>= fun () -> + (ip, Lwt_list.iter_s (set_ip t)) >>=? fun () -> + (netmask, Lwt_list.iter_s (set_ip_netmask t)) >>=? fun () -> + (gateways, set_ip_gateways t) >>=? fun () -> + task) ; (N.listen netif ~header_size:Ethernet_wire.sizeof_ethernet ethif_listener >|= fun _ -> ()) ; - task ; timeout ] >>= fun () -> match get_ip t with From 83ef9db71c4719ac17bcfd989e478b271ab41439 Mon Sep 17 00:00:00 2001 From: Hannes Mehnert Date: Thu, 10 Sep 2020 23:10:09 +0200 Subject: [PATCH 6/6] test: ipv6 stack initialization failure if NA are received (DAD failure) --- test/dune | 3 +- test/test_ipv6.ml | 87 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/test/dune b/test/dune index 6656fe37f..8ea600105 100644 --- a/test/dune +++ b/test/dune @@ -5,7 +5,8 @@ pcap-format duration mirage-random rresult mirage-protocols mirage-stack arp arp-mirage ethernet tcpip.ipv4 tcpip.tcp tcpip.udp tcpip.stack-direct tcpip.icmpv4 tcpip.udpv4-socket tcpip.tcpv4-socket - tcpip.icmpv4-socket tcpip.stack-socket tcpip.ipv6 ipaddr-cstruct)) + tcpip.icmpv4-socket tcpip.stack-socket tcpip.ipv6 ipaddr-cstruct + macaddr-cstruct)) (alias (name runtest) diff --git a/test/test_ipv6.ml b/test/test_ipv6.ml index d9a4e6293..faf6b60fc 100644 --- a/test/test_ipv6.ml +++ b/test/test_ipv6.ml @@ -92,7 +92,8 @@ let create_ethernet backend = ~arpv4:(fun _ -> Lwt.return_unit) ~ipv4:(fun _ -> Lwt.return_unit) ~ipv6) >|= fun _ -> ()), - (fun dst ?size f -> E.write ethif dst `IPv6 ?size f) + (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))) @@ -101,7 +102,7 @@ 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) -> + 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.sizeof_ns in let nd buf = @@ -111,13 +112,13 @@ let dad_na_is_sent () = Ipaddr_cstruct.V6.write_cstruct_exn (Ipaddr.V6.Prefix.network_address solicited_node_prefix address) (Cstruct.shift buf 24); Ipv6_wire.set_ipv6_hlim buf 255; Ipv6_wire.set_ipv6_nhdr buf (Ipv6_wire.protocol_to_int `ICMP); - let icmpbuf = Cstruct.shift buf Ipv6_wire.sizeof_ipv6 in + let hdr, icmpbuf = Cstruct.split buf Ipv6_wire.sizeof_ipv6 in Ipv6_wire.set_ns_ty icmpbuf 135; (* NS *) Ipv6_wire.set_ns_code icmpbuf 0; Ipv6_wire.set_ns_reserved icmpbuf 0l; - Ipaddr_cstruct.V6.write_cstruct_exn address (Cstruct.shift icmpbuf 6); + Ipaddr_cstruct.V6.write_cstruct_exn address (Cstruct.shift icmpbuf 8); Ipv6_wire.set_icmpv6_csum icmpbuf 0; - Ipv6_wire.set_icmpv6_csum icmpbuf @@ Ndpv6.checksum buf []; + Ipv6_wire.set_icmpv6_csum icmpbuf @@ Ndpv6.checksum hdr [icmpbuf]; nd_size and is_na buf = let icmpbuf = Cstruct.shift buf Ipv6_wire.sizeof_ipv6 in @@ -130,8 +131,11 @@ let dad_na_is_sent () = Ipaddr.V6.link_nodes = 0 && Ipv6_wire.get_ipv6_hlim buf = 255 && Ipv6_wire.get_ipv6_nhdr buf = Ipv6_wire.protocol_to_int `ICMP && - Ipv6_wire.get_ns_ty icmpbuf = 136 && - Ipv6_wire.get_ns_code icmpbuf = 0 + Ipv6_wire.get_na_ty icmpbuf = 136 && + Ipv6_wire.get_na_code icmpbuf = 0 && + Ipaddr.V6.compare + (Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.shift icmpbuf 8)) + address = 0 in Lwt.pick [ listen stack; @@ -145,7 +149,76 @@ let dad_na_is_sent () = 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.sizeof_na + Ipv6_wire.sizeof_llopt in + let is_ns buf = + let icmpbuf = Cstruct.shift buf Ipv6_wire.sizeof_ipv6 in + if + Ipv6_wire.get_ipv6_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_ipv6_hlim buf = 255 && + Ipv6_wire.get_ipv6_nhdr buf = Ipv6_wire.protocol_to_int `ICMP && + Ipv6_wire.get_ns_ty icmpbuf = 135 && + Ipv6_wire.get_ns_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_ipv6_version_flow buf 0x60000000l; (* IPv6 *) + Ipv6_wire.set_ipv6_len buf (Ipv6_wire.sizeof_na + Ipv6_wire.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_ipv6_hlim buf 255; + Ipv6_wire.set_ipv6_nhdr buf (Ipv6_wire.protocol_to_int `ICMP); + let hdr, icmpbuf = Cstruct.split buf Ipv6_wire.sizeof_ipv6 in + Ipv6_wire.set_na_ty icmpbuf 136; (* NA *) + Ipv6_wire.set_na_code icmpbuf 0; + Ipv6_wire.set_na_reserved icmpbuf 0x20000000l; + Ipaddr_cstruct.V6.write_cstruct_exn addr (Cstruct.shift icmpbuf 8); + let optbuf = Cstruct.shift icmpbuf Ipv6_wire.sizeof_na in + Ipv6_wire.set_llopt_ty optbuf 2; + Ipv6_wire.set_llopt_len optbuf 1; + Macaddr_cstruct.write_cstruct_exn mac (Cstruct.shift optbuf 2); + Ipv6_wire.set_icmpv6_csum icmpbuf 0; + Ipv6_wire.set_icmpv6_csum 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"); + (Time.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; ]