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
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')));
match t.extern with
| None ->
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
let bstr_ic = Bstr.create (14 + mtu) in
let bstr_oc = Bstr.create (14 + mtu) in
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