This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,187 @@
(*
* Copyright (c) 2010-2011 Anil Madhavapeddy <anil@recoil.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS l SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)
open Lwt.Infix
let src = Logs.Src.create "ipv4" ~doc:"Mirage IPv4"
module Log = (val Logs.src_log src : Logs.LOG)
module Make (Ethernet: Ethernet.S) (Arpv4 : Arp.S) = struct
module Routing = Routing.Make(Log)(Arpv4)
(** IO operation errors *)
type error = [ Tcpip.Ip.error | `Would_fragment | `Ethif of Ethernet.error ]
let pp_error ppf = function
| #Tcpip.Ip.error as e -> Tcpip.Ip.pp_error ppf e
| `Ethif e -> Ethernet.pp_error ppf e
type ipaddr = Ipaddr.V4.t
type callback = src:ipaddr -> dst:ipaddr -> Cstruct.t -> unit Lwt.t
let pp_ipaddr = Ipaddr.V4.pp
type prefix = Ipaddr.V4.Prefix.t
let pp_prefix = Ipaddr.V4.Prefix.pp
type t = {
ethif : Ethernet.t;
arp : Arpv4.t;
cidr: Ipaddr.V4.Prefix.t;
gateway: Ipaddr.V4.t option;
mutable cache: Fragments.Cache.t;
}
let write t ?(fragment = true) ?(ttl = 38) ?src dst proto ?(size = 0) headerf bufs =
Routing.destination_mac t.cidr t.gateway t.arp dst >>= function
| Error `Local ->
Log.warn (fun f -> f "Could not find %a on the local network" Ipaddr.V4.pp dst);
Lwt.return @@ Error (`No_route "no response for IP on local network")
| Error `Gateway when t.gateway = None ->
Log.warn (fun f -> f "Write to %a would require an external route, which was not provided" Ipaddr.V4.pp dst);
Lwt.return @@ Ok ()
| Error `Gateway ->
Log.warn (fun f -> f "Write to %a requires an external route, and the provided %a was not reachable" Ipaddr.V4.pp dst (Fmt.option Ipaddr.V4.pp) t.gateway);
(* when a gateway is specified the user likely expects their traffic to be passed to it *)
Lwt.return @@ Error (`No_route "no route to default gateway to outside world")
| Ok mac ->
(* need first to deal with fragmentation decision - find out mtu *)
let mtu = Ethernet.mtu t.ethif in
(* no options here, always 20 bytes! *)
let hdr_len = Ipv4_wire.sizeof_ipv4 in
let needed_bytes = Cstruct.lenv bufs + hdr_len + size in
let multiple = needed_bytes > mtu in
(* construct the header (will be reused across fragments) *)
if not fragment && multiple then
Lwt.return (Error `Would_fragment)
else
let off =
match fragment, multiple with
| true, true -> 0x2000
| false, false -> 0x4000
| true, false -> 0x0000
| false, true -> assert false (* handled by conditional above *)
in
let hdr =
let src = match src with None -> Ipaddr.V4.Prefix.address t.cidr | Some x -> x in
let id = if multiple then Randomconv.int16 Mirage_crypto_rng.generate else 0 in
Ipv4_packet.{
options = Cstruct.empty ;
src ; dst ; ttl ; off ; id ;
proto = Ipv4_packet.Marshal.protocol_to_int proto }
in
let writeout size fill =
Ethernet.write t.ethif mac `IPv4 ~size fill >|= function
| Error e ->
Log.warn (fun f -> f "Error sending Ethernet frame: %a"
Ethernet.pp_error e);
Error (`Ethif e)
| Ok () -> Ok ()
in
Log.debug (fun m -> m "ip write: mtu is %d, hdr_len is %d, size %d \
payload len %d, needed_bytes %d"
mtu hdr_len size (Cstruct.lenv bufs) needed_bytes) ;
let leftover = ref Cstruct.empty in
(* first fragment *)
let fill buf =
let payload_buf = Cstruct.shift buf hdr_len in
let header_len = headerf payload_buf in
if header_len > size then begin
Log.err (fun m -> m "headers returned length exceeding size") ;
invalid_arg "headerf exceeds size"
end ;
(* need to copy the given payload *)
let len, rest =
Cstruct.fillv ~src:bufs ~dst:(Cstruct.shift payload_buf header_len)
in
leftover := Cstruct.concat rest;
let payload_len = header_len + len in
match Ipv4_packet.Marshal.into_cstruct ~payload_len hdr buf with
| Ok () -> payload_len + hdr_len
| Error msg ->
Log.err (fun m -> m "failure while assembling ip frame: %s" msg) ;
invalid_arg msg
in
writeout (min mtu needed_bytes) fill >>= function
| Error e -> Lwt.return (Error e)
| Ok () ->
if not multiple then
Lwt.return (Ok ())
else
let remaining = Fragments.fragment ~mtu hdr !leftover in
Lwt_list.fold_left_s (fun acc p ->
match acc with
| Error e -> Lwt.return (Error e)
| Ok () ->
let l = Cstruct.length p in
writeout l (fun buf -> Cstruct.blit p 0 buf 0 l ; l))
(Ok ()) remaining
let input t ~tcp ~udp ~default buf =
match Ipv4_packet.Unmarshal.of_cstruct buf with
| Error s ->
Log.info (fun m -> m "error %s while parsing IPv4 frame %a" s Cstruct.hexdump_pp buf);
Lwt.return_unit
| Ok (packet, payload) ->
let of_interest ip =
Ipaddr.V4.(compare ip (Prefix.address t.cidr) = 0
|| compare ip broadcast = 0
|| compare ip (Prefix.broadcast t.cidr) = 0)
in
if not (of_interest packet.dst) then begin
Log.debug (fun m -> m "dropping IP fragment not for us or broadcast %a"
Ipv4_packet.pp packet);
Lwt.return_unit
end else if Cstruct.length payload = 0 then begin
Log.debug (fun m -> m "dropping zero length IPv4 frame %a" Ipv4_packet.pp packet) ;
Lwt.return_unit
end else
let ts = Mirage_mtime.elapsed_ns () in
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
match Ipv4_packet.Unmarshal.int_to_protocol packet.proto with
| Some `TCP -> tcp ~src ~dst payload
| Some `UDP -> udp ~src ~dst payload
| Some `ICMP | None -> default ~proto:packet.proto ~src ~dst payload
let connect
?(no_init = false) ~cidr ?gateway ?(fragment_cache_size = 1024 * 256) ethif arp =
(if no_init then
Lwt.return_unit
else
Arpv4.set_ips arp [Ipaddr.V4.Prefix.address cidr]) >|= fun () ->
let cache = Fragments.Cache.empty fragment_cache_size in
{ ethif; arp; cidr; gateway; cache }
let disconnect _ = Lwt.return_unit
let get_ip t = [Ipaddr.V4.Prefix.address t.cidr]
let configured_ips t = [t.cidr]
let pseudoheader t ?src dst proto len =
let src = match src with None -> Ipaddr.V4.Prefix.address t.cidr | Some x -> x in
Ipv4_packet.Marshal.pseudoheader ~src ~dst ~proto len
let src t ~dst:_ = Ipaddr.V4.Prefix.address t.cidr
let mtu t ~dst:_ = Ethernet.mtu t.ethif - Ipv4_wire.sizeof_ipv4
end