Skip to content

Commit 0da0230

Browse files
committed
arp_handler: use timeout based cache
When the cache is full we drop the dynamic entry with the oldest epoch, if any. This no longer requires an LRU. Signed-off-by: Edwin Török <edwin@etorok.net>
1 parent 4023068 commit 0da0230

File tree

3 files changed

+94
-53
lines changed

3 files changed

+94
-53
lines changed

arp.opam

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ depends: [
1414
"macaddr" {>= "4.0.0"}
1515
"logs"
1616
"mirage-sleep" {>= "4.0.0"}
17-
"lru" {>= "0.3.0"}
1817
"lwt"
1918
"duration"
2019
"ethernet" {>= "3.0.0"}

src/arp_handler.ml

Lines changed: 93 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,106 @@ type 'a entry =
44
| Dynamic of Macaddr.t * int
55
| Pending of 'a * int
66

7+
let[@coverage off] pp_entry now k pp =
8+
function
9+
| Static (m, adv) ->
10+
let adv = if adv then " advertising" else "" in
11+
Format.fprintf pp "%a at %a (static%s)" Ipaddr.V4.pp k Macaddr.pp m adv
12+
| Dynamic (m, t) ->
13+
Format.fprintf pp "%a at %a (timeout in %d)" Ipaddr.V4.pp k
14+
Macaddr.pp m (t - now)
15+
| Pending (_, retries) ->
16+
Format.fprintf pp "%a (incomplete, %d retries left)"
17+
Ipaddr.V4.pp k (retries - now)
18+
719
module M = struct
8-
module M = Map.Make(Ipaddr.V4)
9-
module Present = struct
10-
type t = unit
11-
let weight (_: t) = 1
20+
module IpMap = Map.Make(Ipaddr.V4)
21+
module Timeout = struct
22+
type t = int * Ipaddr.V4.t
23+
24+
let compare (t1, ip1) (t2, ip2) =
25+
match Int.compare t1 t2 with
26+
| 0 -> Ipaddr.V4.compare ip1 ip2
27+
| n -> n
28+
29+
let of_entry k = function
30+
| Static _ | Pending _ -> None
31+
| Dynamic (_, timeout) -> Some (timeout, k)
1232
end
13-
module LRU = Lru.F.Make(Ipaddr.V4)(Present)
1433

15-
type 'a t =
16-
{ map: 'a entry M.t
17-
; mutable dynamic_lru: LRU.t
18-
}
34+
module Timeouts = Set.Make(Timeout)
35+
36+
type !+'a t =
37+
{ map: 'a entry IpMap.t
38+
; values: Timeouts.t
39+
; capacity: int
40+
}
41+
42+
let empty capacity =
43+
{ map = IpMap.empty; values = Timeouts.empty; capacity }
1944

20-
let empty capacity =
21-
{ map = M.empty; dynamic_lru = LRU.empty capacity }
45+
let cardinal t = IpMap.cardinal t.map
2246

23-
let fold f t init =
24-
M.fold f t.map init
2547

26-
let cardinal t = M.cardinal t.map
48+
let invariant t =
49+
assert (cardinal t <= t.capacity);
50+
(* only dynamic values are stored in TimeoutMap, due to functional 'a we can't easily compare it *)
51+
assert (IpMap.cardinal t.map >= Timeouts.cardinal t.values);
52+
assert (Timeouts.for_all (fun ((_, k) as v) ->
53+
Option.compare Timeout.compare (IpMap.find k t.map |> Timeout.of_entry k) (Some v) = 0)
54+
t.values);
2755

28-
let iter f t = M.iter f t.map
56+
assert (IpMap.for_all (fun k v ->
57+
match Timeout.of_entry k v with
58+
| None -> true
59+
| Some v' ->
60+
Timeouts.mem v' t.values) t.map);
61+
assert (Timeouts.for_all (fun ((_, k) as v) ->
62+
match Timeout.of_entry k (IpMap.find k t.map) with
63+
| None -> false
64+
| Some v' -> Timeout.compare v v' = 0
65+
) t.values)
2966

30-
let find k t =
31-
let v = M.find k t.map in
32-
t.dynamic_lru <- LRU.promote k t.dynamic_lru;
33-
v
67+
let find k t = IpMap.find k t.map
3468

35-
let add k v t =
36-
let map = M.add k v t.map
37-
and dynamic_lru = match v with
38-
| Dynamic _ -> LRU.add k () t.dynamic_lru
39-
| _ -> LRU.remove k t.dynamic_lru
69+
let fold f t init = IpMap.fold f t.map init
70+
71+
let iter f t = IpMap.iter f t.map
72+
73+
let remove_kv k v t =
74+
let values =
75+
match v with
76+
| None -> t.values
77+
| Some v -> Timeouts.remove v t.values
4078
in
41-
let map, dynamic_lru =
42-
if LRU.weight t.dynamic_lru > LRU.capacity t.dynamic_lru then begin
43-
match LRU.pop_lru t.dynamic_lru with
44-
| Some ((drop, ()), dynamic_lru) ->
45-
M.remove drop t.map, dynamic_lru
46-
| None -> map, dynamic_lru
47-
end else
48-
map, dynamic_lru
79+
{ t with map = IpMap.remove k t.map
80+
; values }
81+
82+
let add ~logsrc ~epoch k v t =
83+
let values =
84+
match Timeout.of_entry k v with
85+
| None -> t.values
86+
| Some v -> Timeouts.add v t.values
4987
in
50-
{ map; dynamic_lru }
88+
let t =
89+
{ t with map = IpMap.add k v t.map
90+
; values
91+
}
92+
in
93+
if cardinal t > t.capacity then
94+
let (timeout, k) as v = Timeouts.min_elt t.values in
95+
Logs.debug ~src:logsrc
96+
(fun pp -> pp "dropping ARP entry %a (timeout in %d)"
97+
Ipaddr.V4.pp k (timeout - epoch)) ;
98+
remove_kv k (Some v) t
99+
else
100+
t
51101

52102
let remove k t =
53-
{ map = M.remove k t.map; dynamic_lru = LRU.remove k t.dynamic_lru }
103+
match IpMap.find_opt k t.map with
104+
| Some v ->
105+
remove_kv k (Timeout.of_entry k v) t
106+
| None -> t
54107
end
55108

56109
type 'a t = {
@@ -71,19 +124,8 @@ let ips t =
71124

72125
let mac t = t.mac
73126

74-
let[@coverage off] pp_entry now k pp =
75-
function
76-
| Static (m, adv) ->
77-
let adv = if adv then " advertising" else "" in
78-
Format.fprintf pp "%a at %a (static%s)" Ipaddr.V4.pp k Macaddr.pp m adv
79-
| Dynamic (m, t) ->
80-
Format.fprintf pp "%a at %a (timeout in %d)" Ipaddr.V4.pp k
81-
Macaddr.pp m (t - now)
82-
| Pending (_, retries) ->
83-
Format.fprintf pp "%a (incomplete, %d retries left)"
84-
Ipaddr.V4.pp k (retries - now)
85-
86127
let[@coverage off] pp pp t =
128+
M.invariant t.cache;
87129
Format.fprintf pp "mac %a ip %a entries %d timeout %d retries %d@."
88130
Macaddr.pp t.mac
89131
Ipaddr.V4.pp t.ip
@@ -100,7 +142,7 @@ let pending t ip =
100142
let mac0 = Macaddr.of_octets_exn (Cstruct.to_string (Cstruct.create 6))
101143

102144
let alias t ip =
103-
let cache = M.add ip (Static (t.mac, true)) t.cache in
145+
let cache = M.add ~logsrc:t.logsrc ~epoch:t.epoch ip (Static (t.mac, true)) t.cache in
104146
(* see RFC5227 Section 3 why we send out an ARP request *)
105147
let garp = Arp_packet.({
106148
operation = Request ;
@@ -131,7 +173,7 @@ let create ?(cache_size=1024) ?(timeout = 800) ?(retries = 5)
131173
t, Some garp
132174

133175
let static t ip mac =
134-
let cache = M.add ip (Static (mac, false)) t.cache in
176+
let cache = M.add ~logsrc:t.logsrc ~epoch:t.epoch ip (Static (mac, false)) t.cache in
135177
{ t with cache }, pending t ip
136178

137179
let remove t ip =
@@ -186,7 +228,7 @@ let tick t =
186228

187229
let handle_reply t source mac =
188230
let extcache =
189-
let cache = M.add source (Dynamic (mac, t.epoch + t.timeout)) t.cache in
231+
let cache = M.add ~logsrc:t.logsrc ~epoch:t.epoch source (Dynamic (mac, t.epoch + t.timeout)) t.cache in
190232
{ t with cache }
191233
in
192234
match M.find source t.cache with
@@ -271,11 +313,11 @@ let query t ip a =
271313
match M.find ip t.cache with
272314
| exception Not_found ->
273315
let a = a None in
274-
let cache = M.add ip (Pending (a, t.epoch + t.retries)) t.cache in
316+
let cache = M.add ~logsrc:t.logsrc ~epoch:t.epoch ip (Pending (a, t.epoch + t.retries)) t.cache in
275317
{ t with cache }, RequestWait (request t ip, a)
276318
| Pending (x, r) ->
277319
let a = a (Some x) in
278-
let cache = M.add ip (Pending (a, r)) t.cache in
320+
let cache = M.add ~logsrc:t.logsrc ~epoch:t.epoch ip (Pending (a, r)) t.cache in
279321
{ t with cache }, Wait a
280322
| Static (m, _) -> t, Mac m
281323
| Dynamic (m, _) -> t, Mac m

src/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
(wrapped false)
66
(instrumentation
77
(backend bisect_ppx))
8-
(libraries cstruct logs ipaddr macaddr fmt lru))
8+
(libraries cstruct logs ipaddr macaddr fmt))

0 commit comments

Comments
 (0)