Source file dsts.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
module Redirect = struct
  type t = { target: Ipaddr.V6.t; destination: Ipaddr.V6.t }

  let pp ppf t =
    Fmt.pf ppf "{ @[<hov>target=@ %a;@ destination=@ %a;@] }" Ipaddr.V6.pp
      t.target Ipaddr.V6.pp t.destination
end

module Unreachable = struct
  type t = { code: int; destination: Ipaddr.V6.t }

  let pp ppf { code; destination } =
    Fmt.pf ppf "Destination %a unreachable (%d)" Ipaddr.V6.pp destination code
end

module PTB = struct
  type t = { mtu: int; destination: Ipaddr.V6.t }

  let pp ppf t =
    Fmt.pf ppf "Packet too big (mtu:%d, addr:%a)" t.mtu Ipaddr.V6.pp
      t.destination
end

type error = [ `Packet_too_big | `Destination_unreachable of int ]

module Dst = struct
  type t = { pmtu: int; next_hop: Ipaddr.V6.t; errored: error option }

  let weight (_t : t) = 1
end

module Dsts = Lru.F.Make (Ipaddr.V6) (Dst)

type t = { cache: Dsts.t; lmtu: int }

let make ~lmtu capacity = { cache= Dsts.empty capacity; lmtu }

let next_hop addr t =
  match Dsts.find addr t.cache with
  | Some { pmtu; next_hop; errored= None } ->
      Ok (next_hop, pmtu, { t with cache= Dsts.promote addr t.cache })
  | Some { errored= Some err; _ } -> Error (err :> [ `Not_found | error ])
  | None -> Error `Not_found

(* NOTE(dinosaure): by default, we use the Link-MTU for any [addr]. *)

let add t ?mtu:(pmtu = t.lmtu) addr next_hop =
  let value = { Dst.pmtu; next_hop; errored= None } in
  let cache = Dsts.add addr value t.cache in
  { t with cache= Dsts.trim cache }

let clean_old_routers routers t =
  let capacity = Dsts.capacity t.cache in
  let fn addr ({ Dst.next_hop; _ } as value) t =
    if List.mem next_hop routers then t else Dsts.add addr value t
  in
  let cache = Dsts.fold_k fn (Dsts.empty capacity) t.cache in
  { t with cache }

let tick t ~now:_ = function
  | `Redirect (_src, r) ->
      begin match Dsts.find r.Redirect.destination t.cache with
      | Some { Dst.pmtu; _ } ->
          let next_hop = r.Redirect.target in
          let errored = None in
          let value = { Dst.pmtu; next_hop; errored } in
          let cache = Dsts.add r.Redirect.destination value t.cache in
          { t with cache= Dsts.trim cache }
      | None ->
          let next_hop = r.Redirect.target in
          let errored = None in
          let value = { Dst.pmtu= t.lmtu; next_hop; errored } in
          let cache = Dsts.add r.Redirect.destination value t.cache in
          { t with cache= Dsts.trim cache }
      end
  | `Destination_unreachable u ->
      (* RFC 4443: Mark the destination as errored in the cache.
         This prevents further attempts to send to this destination
         until the entry expires or is cleared. *)
      begin match Dsts.find u.Unreachable.destination t.cache with
      | Some { Dst.pmtu; next_hop; _ } ->
          let errored = Some (`Destination_unreachable u.Unreachable.code) in
          let value = { Dst.pmtu; next_hop; errored } in
          let cache = Dsts.add u.Unreachable.destination value t.cache in
          { t with cache= Dsts.trim cache }
      | None ->
          (* No cached entry, create one with the error *)
          let errored = Some (`Destination_unreachable u.Unreachable.code) in
          let value =
            { Dst.pmtu= t.lmtu; next_hop= u.Unreachable.destination; errored }
          in
          let cache = Dsts.add u.Unreachable.destination value t.cache in
          { t with cache= Dsts.trim cache }
      end
  | `Packet_too_big ptb -> begin
      (* RFC 8201: Path MTU Discovery for IPv6
         Update the PMTU for the destination. The new PMTU should be
         at least 1280 (minimum IPv6 MTU) and at most the current PMTU.
         Mark the destination as errored so that [next_hop] returns
         [`Packet_too_big] to the caller, allowing TCP to adjust. *)
      let new_pmtu = Int.max 1280 ptb.PTB.mtu in
      let errored = Some `Packet_too_big in
      match Dsts.find ptb.PTB.destination t.cache with
      | Some { Dst.pmtu; next_hop; _ } ->
          let pmtu = Int.min pmtu new_pmtu in
          let value = { Dst.pmtu; next_hop; errored } in
          let cache = Dsts.add ptb.PTB.destination value t.cache in
          { t with cache= Dsts.trim cache }
      | None ->
          let value =
            { Dst.pmtu= new_pmtu; next_hop= ptb.PTB.destination; errored }
          in
          let cache = Dsts.add ptb.PTB.destination value t.cache in
          { t with cache= Dsts.trim cache }
    end
  | _ -> t