package mnet
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
An implementation of TCP (Transmission Control Protocol) in OCaml for Miou & Solo5
Install
dune-project
Dependency
Authors
Maintainers
Sources
mnet-0.0.3.tbz
sha256=85ceac44091bae441d88789b3ce9efe9e206e9905edb37fb8de10121d89c870a
sha512=cad757a660625c085f7c1f337ca7b85cf0145d1e8ef9886200be75174f51519e3443fe5757e061d76b14852a7082728448a7a484949c4a0ebd3011f328b7a25f
doc/src/mnet.arpv4/ARPv4.ml.html
Source file ARPv4.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 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342let src = Logs.Src.create "mnet.arp" let[@inline always] now () = Mkernel.clock_monotonic () module Log = (val Logs.src_log src : Logs.LOG) module Ethernet = Ethernet let error_msgf fmt = Fmt.kstr (fun msg -> Error (`Msg msg)) fmt module Packet = struct type t = { operation: operation ; src_mac: Macaddr.t ; dst_mac: Macaddr.t ; src_ip: Ipaddr.V4.t ; dst_ip: Ipaddr.V4.t } and operation = Request | Reply let operation_of_int = function | 1 -> Ok Request | 2 -> Ok Reply | n -> error_msgf "Invalid ARPv4 operation (%02x)" n let operation_to_int = function Request -> 1 | Reply -> 2 let guard err fn = if fn () then Ok () else Error err let decode ?(off = 0) str = let ( let* ) = Result.bind in let* () = guard `Invalid_ARPv4_packet @@ fun () -> String.length str - off >= 28 in let operation = String.get_uint16_be str (off + 6) in let* operation = operation_of_int operation in let src_mac = Macaddr.of_octets_exn (String.sub str (off + 8) 6) in let src_ip = Ipaddr.V4.of_int32 (String.get_int32_be str (off + 14)) in let dst_mac = Macaddr.of_octets_exn (String.sub str (off + 18) 6) in let dst_ip = Ipaddr.V4.of_int32 (String.get_int32_be str (off + 24)) in Ok { operation; src_mac; dst_mac; src_ip; dst_ip } let unsafe_encode_into t ?(off = 0) bstr = Bstr.set_uint16_be bstr (off + 0) 1; Bstr.set_uint16_be bstr (off + 2) 0x0800; Bstr.set_uint8 bstr (off + 4) 6; Bstr.set_uint8 bstr (off + 5) 4; Bstr.set_uint16_be bstr (off + 6) (operation_to_int t.operation); let src_mac = Macaddr.to_octets t.src_mac in Bstr.blit_from_string src_mac ~src_off:0 bstr ~dst_off:(off + 8) ~len:6; Bstr.set_int32_be bstr (off + 14) (Ipaddr.V4.to_int32 t.src_ip); let dst_mac = Macaddr.to_octets t.dst_mac in Bstr.blit_from_string dst_mac ~src_off:0 bstr ~dst_off:(off + 18) ~len:6; Bstr.set_int32_be bstr (off + 24) (Ipaddr.V4.to_int32 t.dst_ip); 28 end let mac0 = Macaddr.of_octets_exn (String.make 6 '\000') module Entry = struct type ivar = Macaddr.t Miou.Computation.t type t = | Static of Macaddr.t | Dynamic of { addr: Macaddr.t; epoch: int } | Pending of { ivar: ivar; retry: int } let is_disposable = function Dynamic _ -> true | _ -> false end module Key = struct include Ipaddr.V4 let equal a b = Ipaddr.V4.compare a b = 0 let hash = Hashtbl.hash end module Entries = Table.Make (Key) (Entry) type t = { entries: Entries.t ; macaddr: Macaddr.t ; ipaddr: Ipaddr.V4.t ; timeout: int ; retries: int ; mutable epoch: int ; eth: Ethernet.t ; mutex: Miou.Mutex.t ; condition: Miou.Condition.t ; queue: string Ethernet.packet Queue.t } let alias t ipaddr = begin match Entries.find t.entries ipaddr with | exception Not_found -> () | Pending { ivar; _ } -> ignore (Miou.Computation.try_return ivar t.macaddr) | _ -> () end; Entries.add t.entries ipaddr (Static t.macaddr); let operation = Packet.Request in let src_mac = t.macaddr in let dst_mac = mac0 in let src_ip = ipaddr in let dst_ip = ipaddr in let pkt = { Packet.operation; src_mac; dst_mac; src_ip; dst_ip } in (pkt, Macaddr.broadcast) let write t (arp, dst) = (* NOTE(dinosaure): we already check, in [create] that the MTU is more than [28] bytes. The buffer given by [Ethernet] is also more than [28] bytes. *) let fn = Packet.unsafe_encode_into arp ~off:0 in Ethernet.write_directly_into ~len:28 t.eth ~dst ~protocol:Ethernet.ARPv4 fn let guard err fn = if fn () then Ok () else Error err let macaddr t = t.macaddr let request t dst_ip = let operation = Packet.Request in let src_mac = t.macaddr in let dst_mac = Macaddr.broadcast in let src_ip = t.ipaddr in ({ Packet.operation; src_mac; dst_mac; src_ip; dst_ip }, dst_mac) let reply arp macaddr = let operation = Packet.Reply in let src_mac = macaddr in let dst_mac = arp.Packet.src_mac in let src_ip = arp.Packet.dst_ip in let dst_ip = arp.Packet.src_ip in let pkt = { Packet.operation; src_mac; dst_mac; src_ip; dst_ip } in (pkt, arp.Packet.src_mac) exception Timeout exception Clear let empty_bt = Printexc.get_callstack 0 let timeout = (Timeout, empty_bt) let timeout c = ignore (Miou.Computation.try_cancel c timeout) let clear = (Clear, empty_bt) let clear c = ignore (Miou.Computation.try_cancel c clear) let tick t = let epoch = t.epoch in let fn k v (pkts, to_remove, timeouts) = match v with | Entry.Dynamic { epoch= epoch'; _ } when epoch' <= epoch -> (pkts, k :: to_remove, timeouts) | Dynamic { epoch= epoch'; _ } when epoch' <= epoch + 1 -> (request t k :: pkts, to_remove, timeouts) | Pending { ivar; retry } -> if retry <= t.epoch then (pkts, k :: to_remove, ivar :: timeouts) else (request t k :: pkts, to_remove, timeouts) | _ -> (pkts, to_remove, timeouts) in let outs, to_remove, timeouts = Entries.fold fn t.entries ([], [], []) in List.iter (Entries.remove t.entries) to_remove; List.iter (write t) outs; List.iter timeout timeouts; t.epoch <- t.epoch + 1 let handle_request t arp = let dst = arp.Packet.dst_ip in let src = arp.Packet.src_ip in let src_mac = arp.Packet.src_mac in Log.debug (fun m -> let = Ethernet.tags t.eth in m ~tags "%a:%a: who has %a?" Macaddr.pp src_mac Ipaddr.V4.pp src Ipaddr.V4.pp dst); match Entries.find t.entries dst with | exception Not_found -> () | Static macaddr -> write t (reply arp macaddr) | _ -> () let handle_reply t src macaddr = let entry = Entry.Dynamic { addr= macaddr; epoch= t.epoch + t.timeout } in Log.debug (fun m -> let = Ethernet.tags t.eth in m ~tags "handle ARPv4 reply packet from %a:%a" Macaddr.pp macaddr Ipaddr.V4.pp src); match Entries.find t.entries src with | exception Not_found -> () | Static _ -> if Macaddr.compare macaddr mac0 == 0 then Log.debug (fun m -> let = Ethernet.tags t.eth in m ~tags "ignoring gratuitious ARP from %a using %a" Macaddr.pp macaddr Ipaddr.V4.pp src) | Dynamic { addr= macaddr'; _ } -> if Macaddr.compare macaddr macaddr' != 0 then Log.debug (fun m -> let = Ethernet.tags t.eth in m ~tags "set %a from %a to %a" Ipaddr.V4.pp src Macaddr.pp macaddr' Macaddr.pp macaddr); Entries.add t.entries src entry | Pending { ivar; _ } -> Log.debug (fun m -> m "%a is-at %a" Ipaddr.V4.pp src Macaddr.pp macaddr); ignore (Miou.Computation.try_return ivar macaddr); Entries.add t.entries src entry let input t pkt = match Packet.decode pkt.Ethernet.payload with | Error _ -> let = Ethernet.tags t.eth in Log.err (fun m -> m ~tags "Invalid ARPv4 packet:"); Log.err (fun m -> m ~tags "@[<hov>%a@]" (Hxd_string.pp Hxd.default) pkt.Ethernet.payload) | Ok arp -> if Ipaddr.V4.compare arp.Packet.src_ip arp.Packet.dst_ip == 0 || arp.Packet.operation == Packet.Reply then let mac = arp.Packet.src_mac and src = arp.Packet.src_ip in handle_reply t src mac else handle_request t arp let to_error (exn, _bt) = match exn with Timeout -> `Timeout | Clear -> `Clear | exn -> `Exn exn type error = [ `Timeout | `Clear | `Exn of exn ] let pp_error ppf = function | `Timeout -> Fmt.string ppf "Timeout" | `Clear -> Fmt.string ppf "ARP table reset" | `Exn exn -> Fmt.pf ppf "Unexpected exception: %s" (Printexc.to_string exn) let query t ipaddr = match Entries.find t.entries ipaddr with | exception Not_found -> let ivar = Miou.Computation.create () in let retry = t.epoch + t.retries in let pending = Entry.Pending { ivar; retry } in Entries.add t.entries ipaddr pending; write t (request t ipaddr); Miou.Computation.await ivar |> Result.map_error to_error | Pending { ivar; _ } -> Miou.Computation.await ivar |> Result.map_error to_error | Static addr | Dynamic { addr; _ } -> Ok addr let ask t ipaddr = match Entries.find t.entries ipaddr with | exception Not_found -> None | Pending _ -> None | Static addr | Dynamic { addr; _ } -> Some addr let ips t = let fn k v acc = match v with Entry.Static _ -> k :: acc | _ -> acc in Entries.fold fn t.entries [] let add_ip t ipaddr = match ips t with | [] -> let fn _ = function Entry.Pending { ivar; _ } -> clear ivar | _ -> () in Entries.iter fn t.entries; Entries.reset t.entries; (* TODO(dinosaure): reset the dynamic cache *) write t (alias t ipaddr) | _ -> write t (alias t ipaddr) let set_ips t = function | [] -> let fn _ = function Entry.Pending { ivar; _ } -> clear ivar | _ -> () in Entries.iter fn t.entries; Entries.reset t.entries (* TODO(dinosaure): reset the dynamic cache. *) | ipaddr :: rest -> Entries.iter (fun _ -> function Pending { ivar; _ } -> clear ivar | _ -> ()) t.entries; Entries.reset t.entries; (* TODO(dinosaure): reset the dynamic cache. *) write t (alias t ipaddr); List.iter (add_ip t) rest type daemon = unit Miou.t type event = In of string Ethernet.packet Queue.t | Tick let read_or_sync ?(delay = 1_500_000_000) t = let prm1 = Miou.async @@ fun () -> Miou.Mutex.protect t.mutex @@ fun () -> if Queue.is_empty t.queue then Miou.Condition.wait t.condition t.mutex; let todo = Queue.create () in Queue.transfer t.queue todo; In todo in let prm0 = Miou.async @@ fun () -> Mkernel.sleep delay; Tick in match Miou.await_first [ prm0; prm1 ] with | Ok value -> value | Error exn -> Log.err (fun m -> let = Ethernet.tags t.eth in m ~tags "Unexpected exception: %s" (Printexc.to_string exn)); In (Queue.create ()) let arp ?(delay = 1_500_000_000) t = let rec go rem = let t0 = now () in match read_or_sync ~delay:rem t with | In queue -> let fn = input t in Queue.iter fn queue; let t1 = now () in let rem = rem - (t1 - t0) in let rem = if rem <= 0 then delay else rem in go rem | Tick -> tick t; go delay in go delay let create ?(delay = 1_500_000_000) ?(timeout = 800) ?(retries = 5) ?ipaddr eth = let ( let* ) = Result.bind in let macaddr = Ethernet.macaddr eth in (* enough for ARP packets *) let* () = guard `MTU_too_small @@ fun () -> Ethernet.mtu eth >= 28 in if timeout <= 0 then Fmt.invalid_arg "Arp.create: null or negative timeout"; if retries < 0 then Fmt.invalid_arg "Arg.create: negative retries value"; let unknown = Option.is_none ipaddr in let ipaddr = Option.value ~default:Ipaddr.V4.any ipaddr in let t = { entries= Entries.create 0x10 ; macaddr ; ipaddr ; timeout ; retries ; epoch= 0 ; eth ; mutex= Miou.Mutex.create () ; condition= Miou.Condition.create () ; queue= Queue.create () } in if unknown == false then write t (alias t ipaddr); let prm = Miou.async (fun () -> arp ~delay t) in Ok (prm, t) let transfer t pkt = let payload = Slice_bstr.sub_string pkt.Ethernet.payload ~off:0 ~len:28 in let pkt = { pkt with Ethernet.payload } in Miou.Mutex.protect t.mutex @@ fun () -> Queue.push pkt t.queue; Miou.Condition.signal t.condition let kill = Miou.cancel
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>