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

Revert "Ipv4.Fragments: use a mutable LRU cache (Lru.M.t instead of mutable cache : Lru.F.t):" #423

Merged
merged 1 commit into from
Feb 8, 2020
Merged
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
45 changes: 20 additions & 25 deletions src/ipv4/fragments.ml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,16 @@ end
module K = struct
type t = Ipaddr.V4.t * Ipaddr.V4.t * int * int

let equal a b = a = b

let hash r k = Hashtbl.seeded_hash r k
let compare (src, dst, proto, id) (src', dst', proto', id') =
let (&&&) a b = match a with 0 -> b | x -> x in
let int_cmp : int -> int -> int = compare in
Ipaddr.V4.compare src src' &&&
Ipaddr.V4.compare dst dst' &&&
int_cmp proto proto' &&&
int_cmp id id'
end

module Cache = Lru.M.MakeSeeded(K)(V)
module Cache = Lru.F.Make(K)(V)

(* insert_sorted inserts a fragment in a list, sort is by frag_start, descending *)
let rec insert_sorted ((frag_start, _) as frag) = function
Expand Down Expand Up @@ -121,12 +125,12 @@ let max_duration = Duration.of_sec 10

let process cache ts (packet : Ipv4_packet.t) payload =
let add_trim key value cache =
Cache.add key value cache;
Cache.trim cache
let cache' = Cache.add key value cache in
Cache.trim cache'
in
if packet.off land 0x3FFF = 0 then (* ignore reserved and don't fragment *)
(* fastpath *)
Some (packet, payload)
cache, Some (packet, payload)
else
let offset, more =
(packet.off land 0x1FFF) lsl 3, (* of 8 byte blocks *)
Expand All @@ -137,15 +141,13 @@ let process cache ts (packet : Ipv4_packet.t) payload =
match Cache.find key cache with
| None ->
Log.debug (fun m -> m "%a none found, inserting into cache" Ipv4_packet.pp packet) ;
add_trim key v cache;
None
add_trim key v cache, None
| Some (ts', options, finished, cnt, frags) ->
if Int64.sub ts ts' >= max_duration then begin
Log.warn (fun m -> m "%a found some, but timestamp exceeded duration %a, dropping old segments and inserting new segment into cache" Ipv4_packet.pp packet Duration.pp max_duration) ;
add_trim key v cache;
None
end else begin
Cache.promote key cache;
add_trim key v cache, None
end else
let cache' = Cache.promote key cache in
let all_frags = insert_sorted (offset, payload) frags
and try_reassemble = finished || not more
and options' = if offset = 0 then packet.options else options
Expand All @@ -165,8 +167,7 @@ let process cache ts (packet : Ipv4_packet.t) payload =
| Ok p ->
Log.debug (fun m -> m "%a reassembled to payload %d" Ipv4_packet.pp packet (Cstruct.len p)) ;
let packet' = { packet with options = options' ; off = 0 } in
Cache.remove key cache;
Some (packet', p)
Cache.remove key cache', Some (packet', p)
| Error Bad ->
Log.warn (fun m -> m "%a dropping from cache, bad fragments (%a)"
Ipv4_packet.pp packet
Expand All @@ -175,16 +176,10 @@ let process cache ts (packet : Ipv4_packet.t) payload =
Log.debug (fun m -> m "full fragments: %a"
Fmt.(list ~sep:(unit "@.") Cstruct.hexdump_pp)
(List.map snd all_frags)) ;
Cache.remove key cache;
None
| Error Hole ->
maybe_add_to_cache cache;
None
else begin
maybe_add_to_cache cache;
None
end
end
Cache.remove key cache', None
| Error Hole -> maybe_add_to_cache cache', None
else
maybe_add_to_cache cache', None

(* TODO hdr.options is a Cstruct.t atm, but instead we need to parse all the
options, and distinguish based on the first bit -- only these with the bit
Expand Down
50 changes: 30 additions & 20 deletions src/ipv4/fragments.mli
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,43 @@
identifier, and protocol ID, is received, reassembly is attempted - also on
subsequent packets with the same quadruple. *)

module V : Lru.Weighted with type t = int64 * Cstruct.t * bool * int * (int * Cstruct.t) list
(** The type of values in the fragment cache: a timestamp of the first
received one, IP options (of the first fragment), whether or not the last
fragment was received (the one with more fragments cleared), amount of
received fragments, and a list of pairs of offset and fragment. *)
module V : sig
type t = int64 * Cstruct.t * bool * int * (int * Cstruct.t) list
(** The type of values in the fragment cache: a timestamp of the first
received one, IP options (of the first fragment), whether or not the last
fragment was received (the one with more fragments cleared), amount of
received fragments, and a list of pairs of offset and fragment. *)

module K : Hashtbl.SeededHashedType
with type t = Ipaddr.V4.t * Ipaddr.V4.t * int * int
(** The type of keys in the fragment cache: source IP address, destination
IP address, protocol type, and IP identifier. *)
val weight : t -> int
(** [weight t] is the data length of the received fragments. *)
end

module Cache : Lru.M.S with type k = K.t and type v = V.t
module K : sig
type t = Ipaddr.V4.t * Ipaddr.V4.t * int * int
(** The type of keys in the fragment cache: source IP address, destination
IP address, protocol type, and IP identifier. *)

val compare : t -> t -> int
end

module Cache : sig
include Lru.F.S with type k = K.t and type v = V.t
end

val max_duration : int64
(** [max_duration] is the maximum delta between first and last received
fragment, in nanoseconds. At the moment it is 10 seconds. *)

val process : Cache.t -> int64 -> Ipv4_packet.t -> Cstruct.t ->
(Ipv4_packet.t * Cstruct.t) option
(** [process t timestamp hdr payload] is [t'], a new cache, and maybe a fully
reassembled IPv4 packet. If reassembly fails, e.g. too many fragments, delta
between receive timestamp of first and last packet exceeds {!max_duration},
overlapping packets, these packets will be dropped from the cache. The IPv4
header options are always taken from the first fragment (where offset is
0). If the provided IPv4 header has an fragmentation offset of 0, and the
more fragments bit is not set, the given header and payload is directly
returned. Handles out-of-order fragments gracefully. *)
val process : Cache.t -> int64 -> Ipv4_packet.t -> Cstruct.t -> Cache.t *
(Ipv4_packet.t * Cstruct.t) option (** [process t timestamp hdr payload] is
[t'], a new cache, and maybe a fully reassembled IPv4 packet. If reassembly
fails, e.g. too many fragments, delta between receive timestamp of first and
last packet exceeds {!max_duration}, overlapping packets, these packets
will be dropped from the cache. The IPv4 header options are always taken from
the first fragment (where offset is 0). If the provided IPv4 header has an
fragmentation offset of 0, and the more fragments bit is not set, the given
header and payload is directly returned. Handles out-of-order fragments
gracefully. *)

val fragment : mtu:int -> Ipv4_packet.t -> Cstruct.t -> Cstruct.t list
(** [fragment ~mtu hdr payload] is called with the IPv4 header of the first
Expand Down
8 changes: 5 additions & 3 deletions src/ipv4/static_ipv4.ml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ module Make (R: Mirage_random.S) (C: Mirage_clock.MCLOCK) (Ethernet: Mirage_prot
mutable ip: Ipaddr.V4.t;
network: Ipaddr.V4.Prefix.t;
mutable gateway: Ipaddr.V4.t option;
cache: Fragments.Cache.t;
mutable cache: Fragments.Cache.t;
}

let write t ?(fragment = true) ?(ttl = 38) ?src dst proto ?(size = 0) headerf bufs =
Expand Down Expand Up @@ -147,7 +147,9 @@ module Make (R: Mirage_random.S) (C: Mirage_clock.MCLOCK) (Ethernet: Mirage_prot
Lwt.return_unit
end else
let ts = C.elapsed_ns () in
match Fragments.process t.cache ts packet payload with
let cache, res = Fragments.process t.cache ts packet payload in
t.cache <- cache ;
match res with
| None -> Lwt.return_unit
| Some (packet, payload) ->
let src, dst = packet.src, packet.dst in
Expand All @@ -161,7 +163,7 @@ module Make (R: Mirage_random.S) (C: Mirage_clock.MCLOCK) (Ethernet: Mirage_prot
Arpv4.set_ips arp [ip] >>= fun () ->
(* TODO currently hardcoded to 256KB, should be configurable
and maybe limited per-src/dst-ip as well? *)
let cache = Fragments.Cache.create ~random:true fragment_cache_size in
let cache = Fragments.Cache.empty fragment_cache_size in
Lwt.return { ethif; arp; ip; network; gateway ; cache }

let disconnect _ = Lwt.return_unit
Expand Down
113 changes: 44 additions & 69 deletions test/test_ipv4.ml
Original file line number Diff line number Diff line change
Expand Up @@ -64,152 +64,129 @@ let gray =
Cstruct.memset buf 0x55 ;
buf

let below_max = Int64.sub Fragments.max_duration 1L
let empty_cache = Fragments.Cache.empty 1000

let basic_fragments payload () =
let cache = Fragments.Cache.create 1000 in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__
(Some (test_packet, payload))
(Fragments.process cache 0L test_packet payload));
(snd @@ Fragments.process empty_cache 0L test_packet payload)) ;
let off_packet = { test_packet with off = 1 } in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__
None
(Fragments.process cache 0L off_packet payload));
(snd @@ Fragments.process empty_cache 0L off_packet payload)) ;
Lwt.return_unit

let basic_reassembly () =
let empty_cache = Fragments.Cache.create 1000 in
let more_frags = { test_packet with off = mf } in
let res = Fragments.process empty_cache 0L more_frags black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let cache, res = Fragments.process empty_cache 0L more_frags black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
let off_packet = { test_packet with off = 2 } in
Alcotest.(check (option (pair ipv4_packet cstruct)) "reassembly of two segments works"
(Some (test_packet, Cstruct.append black white))
(Fragments.process empty_cache 0L off_packet white));
(snd @@ Fragments.process cache 0L off_packet white)) ;
Lwt.return_unit

let basic_reassembly_timeout () =
let cache = Fragments.Cache.create 1000 in
let more_frags = { test_packet with off = mf } in
let res = Fragments.process cache 0L more_frags black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let cache, res = Fragments.process empty_cache 0L more_frags black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
let off_packet = { test_packet with off = 2 } in
let below_max = Int64.sub Fragments.max_duration 1L in
Alcotest.(check (option (pair ipv4_packet cstruct)) "even after just before max duration"
(Some (test_packet, Cstruct.append black white))
(snd @@ Fragments.process cache below_max off_packet white)) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) "none after max duration"
None
(Fragments.process cache Fragments.max_duration off_packet white));
Lwt.return_unit

let multiple_reassembly () =
let cache = Fragments.Cache.create 1000 in
let more_frags = { test_packet with off = mf } in
let res = Fragments.process cache 0L more_frags black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
(snd @@ Fragments.process cache Fragments.max_duration off_packet white)) ;
let more_off_packet = { test_packet with off = mf lor 2 } in
let res = Fragments.process cache below_max more_off_packet gray in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let cache, res = Fragments.process cache below_max more_off_packet gray in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
let final_packet = { test_packet with off = 4 } in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__
(Some (test_packet, Cstruct.concat [ black; gray; white]))
(Fragments.process cache below_max final_packet white));
Lwt.return_unit

let multiple_reassembly_timeout () =
let cache = Fragments.Cache.create 1000 in
let more_frags = { test_packet with off = mf } in
let res = Fragments.process cache 0L more_frags black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let more_off_packet = { test_packet with off = mf lor 2 } in
let res = Fragments.process cache below_max more_off_packet gray in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let final_packet = { test_packet with off = 4 } in
(snd @@ Fragments.process cache below_max final_packet white)) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__
None
(Fragments.process cache Fragments.max_duration final_packet white));
(snd @@ Fragments.process cache Fragments.max_duration off_packet white)) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
Lwt.return_unit

let reassembly_out_of_order () =
let cache = Fragments.Cache.create 1000 in
let more_frags = { test_packet with off = mf } in
let off_packet = { test_packet with off = 2 } in
let res = Fragments.process cache 0L off_packet gray in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let cache, res = Fragments.process empty_cache 0L off_packet gray in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) "reassembly of two segments works"
(Some (test_packet, Cstruct.append black gray))
(Fragments.process cache 0L more_frags black));
(snd @@ Fragments.process cache 0L more_frags black)) ;
Lwt.return_unit

let reassembly_multiple_out_of_order packets final_payload () =
let cache = Fragments.Cache.create 1000 in
let res = List.fold_left (fun res (off, payload) ->
Alcotest.(check (option (pair ipv4_packet cstruct))
__LOC__ None res);
let _, res = List.fold_left (fun (cache, res) (off, payload) ->
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
let packet = { test_packet with off } in
Fragments.process cache 0L packet payload)
None packets
(empty_cache, None) packets
in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__
(Some (test_packet, final_payload))
res) ;
Lwt.return_unit

let basic_overlaps () =
let cache = Fragments.Cache.create 1000 in
let more_frags = { test_packet with off = mf } in
let off_packet = { test_packet with off = 1 } in
let res = Fragments.process cache 0L off_packet black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let cache, res = Fragments.process empty_cache 0L off_packet black in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None
(Fragments.process cache 0L more_frags white));
(snd @@ Fragments.process cache 0L more_frags white)) ;
Lwt.return_unit

let basic_other_ip_flow () =
let cache = Fragments.Cache.create 1000 in
let more_frags = { test_packet with off = mf } in
let res = Fragments.process cache 0L more_frags black in
let cache, res = Fragments.process empty_cache 0L more_frags black in
let off_packet = { test_packet with off = 2 ; src = Ipaddr.V4.of_string_exn "127.0.0.2" } in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None
(Fragments.process cache 0L off_packet white));
(snd @@ Fragments.process cache 0L off_packet white)) ;
let off_packet' = { test_packet with off = 2 ; proto = 25 } in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None
(Fragments.process cache 0L off_packet' white));
(snd @@ Fragments.process cache 0L off_packet' white)) ;
Lwt.return_unit

let max_fragment () =
let cache = Fragments.Cache.create 1000 in
let all_16 = [ white; gray; black; white;
white; gray; black; white;
white; gray; black; white;
white; gray; black ; gray ]
in
let res, off =
List.fold_left (fun (res, off) payload ->
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let (cache, res), off =
List.fold_left (fun ((cache, res), off) payload ->
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
let r = Fragments.process cache 0L { test_packet with off = off lor mf } payload in
(r, Cstruct.len payload / 8 + off))
(None, 0)
((empty_cache, None), 0)
all_16
in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__
(Some (test_packet, Cstruct.concat (all_16 @ [white ])))
(Fragments.process cache 0L { test_packet with off } white));
let res = Fragments.process cache 0L { test_packet with off = off lor mf } white in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
(snd @@ Fragments.process cache 0L { test_packet with off } white)) ;
let cache, res = Fragments.process cache 0L { test_packet with off = off lor mf } white in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__
None
(Fragments.process cache 0L { test_packet with off = off + 2 } black));
(snd @@ Fragments.process cache 0L { test_packet with off = off + 2 } black)) ;
Lwt.return_unit

let none_returned packets () =
let cache = Fragments.Cache.create 1000 in
let res = List.fold_left (fun res (off, payload) ->
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
let _, res = List.fold_left (fun (cache, res) (off, payload) ->
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
let packet = { test_packet with off } in
Fragments.process cache 0L packet payload)
None packets
(empty_cache, None) packets
in
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res);
Alcotest.(check (option (pair ipv4_packet cstruct)) __LOC__ None res) ;
Lwt.return_unit

let ins_all_positions x l =
Expand Down Expand Up @@ -274,8 +251,6 @@ let suite = [
[ 0 ; 1 ; 2 ; 10 ; 100 ; 1000 ; 5000 ; 10000 ] @ [
"basic reassembly", `Quick, basic_reassembly;
"basic reassembly timeout", `Quick, basic_reassembly_timeout;
"multiple reassembly", `Quick, multiple_reassembly;
"multiple reassembly timeout", `Quick, multiple_reassembly_timeout;
"reassembly out of order", `Quick, reassembly_out_of_order ;
"other ip flow", `Quick, basic_other_ip_flow ;
"maximum amount of fragments", `Quick, max_fragment ] @
Expand Down