Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Initial attempt at rfc8106 #490

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/ipv6/ipv6_wire.ml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ let get_opt_prefix_on_link buf =
let get_opt_prefix_autonomous buf =
get_opt_prefix_reserved1 buf land 0x40 <> 0

[%%cstruct
type opt_rdnss_header = {
ty: uint8_t;
len: uint8_t;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hereafter we need a reserved : uint16_t (since there's some padding, and the lifetime starts at offset 4)

reserved: uint16_t;
rdnss_lifetime: uint32_t;
} [@@big_endian]
]

[%%cstruct
type opt = {
ty: uint8_t;
Expand Down
91 changes: 91 additions & 0 deletions src/ipv6/ndpv6.ml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ References:

- Multicast Listener Discovery Version 2 (MLDv2) for IPv6
http://tools.ietf.org/html/rfc3810

- IPv6 Router Advertisement Options for DNS Configuration
https://tools.ietf.org/html/rfc8106
*)

let src = Logs.Src.create "ndpc6" ~doc:"Mirage IPv6 discovery"
Expand Down Expand Up @@ -255,12 +258,17 @@ type pfx =
pfx_preferred_lifetime : time option;
pfx_prefix : Ipaddr.Prefix.t }

type rdnss =
{ rdnss_lifetime: time option;
rdnss_addresses: Ipaddr.t list }

type ra =
{ ra_cur_hop_limit : int;
ra_router_lifetime : time;
ra_reachable_time : time option;
ra_retrans_timer : time option;
ra_slla : Macaddr.t option;
ra_rdnss : rdnss list;
ra_prefix : pfx list }

type na =
Expand Down Expand Up @@ -665,6 +673,60 @@ module NeighborCache = struct
| Not_found -> false
end

module RDNSSList = struct

type t =
(Ipaddr.t * time) list

let empty =
[]

let to_list rdnssl =
List.map fst rdnssl

let add rdnssl ~now ?(lifetime = Duration.of_year 1) ip =
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From RFC 8106:
The value of Lifetime SHOULD by default be at least 3 * MaxRtrAdvInterval

Can we use that as default, and not 1 year :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to access MaxRtrAdvInterval here as it's something that's set by the router.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC 4861 says about MaxRtrAdvInterval:

MUST be no less than 4 seconds and no greater than 1800 seconds.

So perhaps 1½ hours is reasonable.

Reading the code and some parts of RFC 8106 it seems to me that the lifetime is only None when it's zero in the packet, and in the rfc it says

A value of zero means that the RDNSS addresses MUST no longer be used

So maybe we should either promote lifetime to a regular labeled argument and optionally assert lifetime is greater than zero, or we should not have a default value and do nothing if lifetime is None.

I wonder if something similar applies to RouterList.add where there's a FIXME comment about the default lifetime...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... or perhaps instead of doing nothing we should actively remove that IP address from the list.

(ip, Int64.add now lifetime) :: rdnssl

let tick rdnssl ~now =
List.filter (fun (_, t) -> t > now) rdnssl

let handle_ra rdnssl ~now ~src ~lft =
match List.mem_assoc src rdnssl with
| true ->
let rdnssl = List.remove_assoc src rdnssl in
if lft > 0L then begin
Log.info (fun f -> f "RA: Refreshing Nameserver: src=%a lft=%Lu" Ipaddr.pp src lft);
(src, Int64.add now lft) :: rdnssl, []
end else begin
Log.info (fun f -> f "RA: Nameserver Expired: src=%a" Ipaddr.pp src);
rdnssl, []
end
| false ->
if lft > 0L then begin
Log.debug (fun f -> f "RA: Adding Nameserver: src=%a" Ipaddr.pp src);
(add rdnssl ~now ~lifetime:lft src), []
end else
rdnssl, []

let add rdnssl ~now:_ ip =
match List.mem_assoc ip rdnssl with
| true -> rdnssl
| false -> (ip, Duration.of_year 1) :: rdnssl

let select rdnssl reachable ip =
let rec loop = function
| [] ->
begin match rdnssl with
| [] -> ip, rdnssl
| (ip, _) as r :: rest ->
ip, rest @ [r]
end
| (ip, _) :: _ when reachable ip -> ip, rdnssl
| _ :: rest -> loop rest
in
loop rdnssl
end

module RouterList = struct

type t =
Expand Down Expand Up @@ -741,6 +803,7 @@ module Parser = struct
| TLLA of Macaddr.t
| MTU of int
| PREFIX of pfx
| RDNSS of rdnss

let rec parse_options1 opts =
if Cstruct.length opts >= Ipv6_wire.sizeof_opt then
Expand Down Expand Up @@ -777,6 +840,25 @@ module Parser = struct
{pfx_on_link; pfx_autonomous; pfx_valid_lifetime; pfx_preferred_lifetime; pfx_prefix}
in
PREFIX pfx :: parse_options1 opts
| 25, 3 ->
let rdnss_lifetime =
let n = Ipv6_wire.get_opt_rdnss_header_rdnss_lifetime opt in
match n with
| 0l -> None
| n -> Some (Int64.of_int32 n)
in
let decode_ns off = ipaddr_of_cstruct (Cstruct.shift opt off) in
let rec collect_ns acc = function
| 0 -> acc
| n ->
let ns = decode_ns (Ipv6_wire.sizeof_opt_rdnss_header + n * 16) in
collect_ns (ns :: acc) (n - 1)
in
let rdnss_addresses = collect_ns [] (Ipv6_wire.get_opt_rdnss_header_len opt - 1) in
let rdnss =
{rdnss_lifetime; rdnss_addresses}
in
RDNSS rdnss :: parse_options1 opts
| ty, len ->
Log.info (fun f -> f "ND6: Unsupported ND option in RA: ty=%d len=%d" ty len);
parse_options1 opts
Expand Down Expand Up @@ -1133,6 +1215,7 @@ let local ~handle_ra ~now ~random mac =
let ctx =
{ neighbor_cache = NeighborCache.empty;
prefix_list = PrefixList.link_local;
rdnss_list = RDNSSList.empty;
router_list = RouterList.empty;
mac = mac;
address_list = AddressList.empty;
Expand Down Expand Up @@ -1315,6 +1398,7 @@ let tick ~now ctx =
let address_list, actions = AddressList.tick ctx.address_list ~now ~retrans_timer in
let prefix_list = PrefixList.tick ctx.prefix_list ~now in
let neighbor_cache, actions' = NeighborCache.tick ctx.neighbor_cache ~now ~retrans_timer in
let rdnss_list = RDNSSList.tick ctx.rdnss_list ~now in
let router_list = RouterList.tick ctx.router_list ~now in
let ctx = {ctx with address_list; prefix_list; neighbor_cache; router_list} in
let actions = actions @ actions' in
Expand All @@ -1327,6 +1411,13 @@ let add_prefix ~now ctx pfx =
let get_prefix ctx =
PrefixList.to_list ctx.prefix_list

let add_rdnss ~now ctx ips =
let rdnss_list = List.fold_left (RDNSSList.add ~now) ctx.rdnss_list ips in
{ctx with rdnss_list}

let get_rdnss ctx =
RDNSSList.to_list ctx.rdnss_list

let add_routers ~now ctx ips =
let router_list = List.fold_left (RouterList.add ~now) ctx.router_list ips in
{ctx with router_list}
Expand Down
8 changes: 8 additions & 0 deletions src/ipv6/ndpv6.mli
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
type buffer = Cstruct.t
type ipaddr = Ipaddr.V6.t
type prefix = Ipaddr.V6.Prefix.t
type rdnss = Ipaddr.V6.t list
type time = int64

val ipaddr_of_cstruct : buffer -> ipaddr
Expand Down Expand Up @@ -74,6 +75,13 @@ val add_prefix : now:time -> context -> prefix -> context
val get_prefix : context -> prefix list
(** [get_prefix ctx] returns the list of local prefixes known to [ctx]. *)

val add_rdnss : now:time -> context -> ipaddr list -> context
(** [add_rdnss ~now ctx ips] adds a list of nameserver addresses to [ctx] to be
used as nameservers. *)

val get_rdnss : context -> ipaddr list
(** [get_rdnss ctx] returns the list of nameserver addresses known to [ctx]. *)

val add_routers : now:time -> context -> ipaddr list -> context
(** [add_routers ~now ctx ips] adds a list of gateways to [ctx] to be used for
routing. *)
Expand Down