Source file ethernet.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
let src = Logs.Src.create "mnet.ethernet"

module Log = (val Logs.src_log src : Logs.LOG)

module Packet = struct
  type protocol = ARPv4 | IPv4 | IPv6
  type t = { src: Macaddr.t; dst: Macaddr.t; protocol: protocol option }

  let guard err fn = if fn () then Ok () else Error err

  let protocol_of_int = function
    | 0x0806 -> Some ARPv4
    | 0x0800 -> Some IPv4
    | 0x86dd -> Some IPv6
    | _ -> None

  let protocol_to_int = function
    | ARPv4 -> 0x0806
    | IPv4 -> 0x0800
    | IPv6 -> 0x86dd

  let decode bstr ~len =
    let ( let* ) = Result.bind in
    let* () = guard `Invalid_ethernet_packet @@ fun () -> len >= 14 in
    let dst = Macaddr.of_octets_exn (Bstr.sub_string bstr ~off:0 ~len:6) in
    let src = Macaddr.of_octets_exn (Bstr.sub_string bstr ~off:6 ~len:6) in
    let protocol = Bstr.get_uint16_be bstr 12 in
    let protocol = protocol_of_int protocol in
    let payload = Slice_bstr.make ~off:14 ~len:(len - 14) bstr in
    Ok ({ src; dst; protocol }, payload)

  let encode_into t ?(off = 0) bstr =
    match t.protocol with
    | None ->
        Fmt.invalid_arg
          "Ethernet.Packet.encode_into: you must specify a protocol"
    | Some protocol ->
        let protocol = protocol_to_int protocol in
        Bstr.blit_from_string (Macaddr.to_octets t.dst) ~src_off:0 bstr
          ~dst_off:(off + 0) ~len:6;
        Bstr.blit_from_string (Macaddr.to_octets t.src) ~src_off:0 bstr
          ~dst_off:(off + 6) ~len:6;
        Bstr.set_uint16_be bstr 12 protocol
end

type protocol = Packet.protocol = ARPv4 | IPv4 | IPv6

type t = {
    net: Mkernel.Net.t
  ; mutable handler: handler
  ; mtu: int
  ; mac: Macaddr.t
  ; tags: Logs.Tag.set
  ; bstr_ic: Bstr.t
  ; bstr_oc: Bstr.t
  ; extern: extern option
  ; cnt: int Atomic.t
}

and 'a packet = {
    src: Macaddr.t option
  ; dst: Macaddr.t
  ; protocol: Packet.protocol
  ; payload: 'a
}

and extern = External : 'net hypercalls -> extern [@@unboxed]

and 'net hypercalls = {
    device: 'net
  ; swr: 'net -> ?off:int -> ?len:int -> Bstr.t -> unit
  ; srd: 'net -> ?off:int -> ?len:int -> Bstr.t -> int
}

and handler = Slice_bstr.t packet -> unit

exception Packet_ignored

let mac { mac; _ } = mac
let uninteresting_packet _ = raise_notrace Packet_ignored

let write_directly_into t ?len:plus (packet : (Bstr.t -> int) packet) =
  let fn = packet.payload in
  let src = Option.value ~default:t.mac packet.src in
  let tags = Logs.Tag.add Mnet_tags.mac src Logs.Tag.empty in
  let pkt = { Packet.src; dst= packet.dst; protocol= Some packet.protocol } in
  (* NOTE(dinosaure): clean-up our buffer. *)
  Packet.encode_into pkt ~off:0 t.bstr_oc;
  let bstr = Bstr.sub t.bstr_oc ~off:14 ~len:(Bstr.length t.bstr_oc - 14) in
  Bstr.memset bstr ~off:0 ~len:(Bstr.length bstr) '\000';
  let plus' = fn bstr in
  Option.iter (fun plus -> assert (plus = plus')) plus;
  Log.debug (fun m ->
      m ~tags "write ethernet packet src:%a -> dst:%a (%d byte(s))" Macaddr.pp
        src Macaddr.pp packet.dst plus');
  Log.debug (fun m ->
      m ~tags "@[<hov>%a@]"
        (Hxd_string.pp Hxd.default)
        (Bstr.sub_string t.bstr_oc ~off:0 ~len:(14 + plus')));
  (* TODO(dinosaure): we must figure out about the impact of such branch. We
     also should compare when we directly use [Mkernel.net.write] or if we can
     wrap [read]/[write] into an [External] value (to simplify the API). *)
  match t.extern with
  | None ->
      (* TODO(dinosaure): use [Mkernel.Net.write_into]. *)
      Mkernel.Net.write_bigstring t.net ~off:0 ~len:(14 + plus') t.bstr_oc
  | Some (External { device; swr; _ }) ->
      swr device ~off:0 ~len:(14 + plus') t.bstr_oc

let of_interest t dst =
  Macaddr.compare dst t.mac == 0 || Macaddr.is_unicast dst == false

let handler t bstr ~len =
  if len >= 14 then
    let tags = t.tags in
    match Packet.decode bstr ~len with
    | Error _ ->
        let str = Bstr.sub_string t.bstr_ic ~off:0 ~len in
        Log.err (fun m -> m ~tags "Invalid Ethernet packet");
        Log.err (fun m -> m ~tags "@[<hov>%a@]" (Hxd_string.pp Hxd.default) str)
    | Ok ({ Packet.protocol= Some protocol; src; dst }, payload) ->
        begin try
          if of_interest t dst then
            t.handler { src= Some src; dst; protocol; payload }
        with
        | Packet_ignored -> ()
        | exn ->
            Log.err (fun m ->
                m ~tags "Unexpected exception from the user's handler: %s"
                  (Printexc.to_string exn))
        end
    | Ok _ -> ()

let rec daemon t =
  let len =
    match t.extern with
    | None -> Mkernel.Net.read_bigstring t.net t.bstr_ic
    | Some (External { device; srd; _ }) -> srd device t.bstr_ic
  in
  handler t t.bstr_ic ~len; daemon t

let write_directly_into t ?len ?src ~dst ~protocol fn =
  let pkt = { src; dst; protocol; payload= fn } in
  write_directly_into t ?len pkt

let guard err fn = if fn () then Ok () else Error err

type daemon = unit Miou.t

let create ?(mtu = 1500) ?(handler = uninteresting_packet) ?hypercalls:extern
    mac net =
  let ( let* ) = Result.bind in
  let* () = guard `MTU_too_small @@ fun () -> mtu > 14 in
  (* enough for Ethernet packets *)
  let bstr_ic = Bstr.create (14 + mtu) in
  let bstr_oc = Bstr.create (14 + mtu) in
  (* NOTE(dinosaure): the first [Bstr.sub] does a [malloc()], then any
     [Bstr.sub] are cheap. We should use [Slice] instead of [Bstr]. TODO! *)
  let bstr_ic = Bstr.sub bstr_ic ~off:0 ~len:(14 + mtu) in
  let bstr_oc = Bstr.sub bstr_oc ~off:0 ~len:(14 + mtu) in
  let tags = Logs.Tag.empty in
  let tags = Logs.Tag.add Mnet_tags.mac mac tags in
  let cnt = Atomic.make 0 in
  let t = { net; handler; mtu; mac; tags; bstr_ic; bstr_oc; extern; cnt } in
  let daemon = Miou.async @@ fun () -> daemon t in
  Ok (daemon, t)

let mtu { mtu; _ } = mtu
let macaddr { mac; _ } = mac
let tags { tags; _ } = tags

let set_handler t handler =
  Atomic.incr t.cnt;
  t.handler <- handler;
  let tags = t.tags in
  if Atomic.get t.cnt > 1 then
    Log.warn (fun m -> m ~tags "Ethernet handler modified more than once")

let extend_handler_with t handler =
  let handler pkt = try t.handler pkt with Packet_ignored -> handler pkt in
  t.handler <- handler

let kill = Miou.cancel