Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
assemble.ml1 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(* * Copyright (c) 2015 Thomas Leonard <talex5@gmail.com> * Copyright (c) 2026 Pierre Alain <pierre.alain@tuta.io> * * 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 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. *) let src = Logs.Src.create "assemble" ~doc:"mirage-net-xen.assemble" module Log = (val Logs.src_log src : Logs.LOG) type fragment = { id: int; offset: int; size: int; gref: int32; } type packet = { total_size: int; fragments: fragment list; (* Ring ids of the slots that extra_info descriptors consumed. Such a slot cost the reader whatever a slot costs, a page on the receive ring, but carries no data and yields no fragment, so nothing else would give it back. Derived by position: a descriptor sits in the slot after the message it qualifies and ids run consecutively. That only holds where the reader assigned the ids, so a backend reading ids its peer chose must not use these. *) extra_ids: int list; } (* [Error frags] reports the fragments of a packet that could not be assembled, so the caller can release their pages and grants rather than leak them. *) type assembled = (packet, fragment list) result (* The rings differ in the first message only. A transmit request announces the whole packet, since the sender knows it; a receive response cannot, being written as its page is filled, so it carries its own size like the rest. https://github.com/xen-project/xen/blob/RELEASE-4.19.4/xen/include/public/io/netif.h#L757-L781 *) module type CHANNEL = sig type t type error val name : string val compute_sizes_read : declared_size:int -> rest_sizes:int list -> (int * int) val read : Cstruct.t -> (t, string) result val id : t -> int val offset : t -> int val flags : t -> Flags.t val size : t -> (int, error) result val gref : t -> int32 val set_extras : t -> Extra.t list -> t val extras : t -> Extra.t list end module RX_Channel : CHANNEL with type t = RX.Response.t and type error = int = struct let name = "RX" (* Every response reports the bytes written in its own page, the first one included, so the packet is their sum. https://github.com/xen-project/xen/blob/RELEASE-4.19.4/xen/include/public/io/netif.h#L834-L839 *) let compute_sizes_read ~declared_size ~rest_sizes = let total = declared_size + List.fold_left (+) 0 rest_sizes in (total, declared_size) type t = RX.Response.t type error = int let read = RX.Response.read let id msg = msg.RX.Response.id let offset msg = msg.RX.Response.offset let flags msg = msg.RX.Response.flags let size msg = msg.RX.Response.size (* The page is the reader's own, found from the id. *) let gref _msg = 0l let set_extras msg extras = {msg with RX.Response.extras = extras} let extras msg = msg.RX.Response.extras end module TX_Channel : CHANNEL with type t = TX.Request.t and type error = TX.Request.error = struct let name = "TX" (* Here declared_size is the whole packet and the following requests carry only their own fragment, so the first fragment is what remains. Same subtraction as netback, first->size -= txp->size. https://github.com/xen-project/xen/blob/RELEASE-4.19.4/xen/include/public/io/netif.h#L776-L781 https://github.com/torvalds/linux/blob/v6.12/drivers/net/xen-netback/netback.c#L304 *) let compute_sizes_read ~declared_size ~rest_sizes = let total = declared_size in let first_frag = declared_size - List.fold_left (+) 0 rest_sizes in (total, first_frag) type t = TX.Request.t type error = TX.Request.error let read = TX.Request.read let id msg = msg.TX.Request.id let offset msg = msg.TX.Request.offset let flags msg = msg.TX.Request.flags let size msg = TX.Request.size msg let gref msg = msg.TX.Request.gref let set_extras msg extras = {msg with TX.Request.extras = extras} let extras msg = msg.TX.Request.extras end module Make_Reader(C : CHANNEL) = struct (* A descriptor sits in the slot after the message it qualifies and carries no data, so it has to be recognised rather than read as another message. *) let collect_messages ~with_extras ack_fn = let messages = ref [] in let pending_msg = ref None in let pending_extras = ref [] in ack_fn (fun slot -> if with_extras then ( match !pending_msg with | Some base_msg -> begin match Extra.read slot with | Error e -> Log.warn (fun f -> f "[%s] Drop bad extra_info: %s" C.name e); messages := base_msg :: !messages; pending_msg := None; pending_extras := [] | Ok extra -> pending_extras := extra :: !pending_extras; (* Bit 0 of flags: 0 = last extra, 1 = more extras *) if extra.Extra.flags land 1 = 0 then ( messages := C.set_extras base_msg (List.rev !pending_extras) :: !messages; pending_msg := None; pending_extras := [] ) end | None -> match C.read slot with | Error e -> Log.warn (fun f -> f "[%s] Bad msg: %s" C.name e) | Ok msg -> if Flags.(mem extra_info) (C.flags msg) then pending_msg := Some msg else messages := msg :: !messages ) else ( match C.read slot with | Error e -> Log.warn (fun f -> f "[%s] Bad msg: %s" C.name e) | Ok msg -> messages := msg :: !messages ) ); if with_extras then ( match !pending_msg with | Some base_msg -> Log.warn (fun f -> f "[%s] Orphan message recovered" C.name); messages := C.set_extras base_msg (List.rev !pending_extras) :: !messages | None -> ()); List.rev !messages (* Enough to release the page and grant, without trusting the size field. *) let fragment_of_msg msg = { id = C.id msg; offset = C.offset msg; size = 0; gref = C.gref msg } let rec group_into_packets = function | [] -> [] | msg :: rest -> if Flags.(mem more_data) (C.flags msg) then begin match collect_fragments rest with | Ok (frags, remaining) -> make_packet msg frags :: group_into_packets remaining | Error frags -> (* Nothing left to resynchronise on: report what we hold and stop. *) Log.warn (fun f -> f "[%s] Truncated fragment chain, dropping %d messages" C.name (1 + List.length frags)); [ Error (List.map fragment_of_msg (msg :: frags)) ] end else make_packet msg [] :: group_into_packets rest and collect_fragments = function | [] -> Error [] | msg :: rest -> if Flags.(mem more_data) (C.flags msg) then begin match collect_fragments rest with | Ok (more, remaining) -> Ok (msg :: more, remaining) | Error frags -> Error (msg :: frags) end else Ok ([msg], rest) and make_packet first_msg continuation_msgs = let declared_size = C.size first_msg in let rest_sizes = List.map C.size continuation_msgs in (* A non-positive status is a normal outcome, not a reason to abort the poll. *) match declared_size, List.partition_map (function Ok x -> Left x | Error e -> Right e) rest_sizes with | Ok _, (_, (_ :: _ as es)) | Error _, (_, es) -> Log.warn (fun f -> f "[%s] Dropping packet: %d/%d messages carry an error status" C.name (List.length es + if Result.is_error declared_size then 1 else 0) (List.length rest_sizes + 1)); Error (List.map fragment_of_msg (first_msg :: continuation_msgs)) | Ok declared_size, (rest_sizes, []) -> let extra_ids = List.mapi (fun k _ -> (C.id first_msg + k + 1) land 0xffff) (C.extras first_msg) in let total_size, first_fragment_size = C.compute_sizes_read ~declared_size ~rest_sizes in (* The TX subtraction goes negative if the peer announces sizes that do not add up. *) if total_size < 0 || first_fragment_size < 0 then begin Log.warn (fun f -> f "[%s] Dropping packet with inconsistent sizes (total=%d first=%d)" C.name total_size first_fragment_size); Error (List.map fragment_of_msg (first_msg :: continuation_msgs)) end else let first_fragment = { id = C.id first_msg; offset = C.offset first_msg; size = first_fragment_size; gref = C.gref first_msg; } in let rest_fragments = List.map2 (fun msg size -> { id = C.id msg; offset = C.offset msg; size; gref = C.gref msg } ) continuation_msgs rest_sizes in Ok { total_size; fragments = first_fragment :: rest_fragments; extra_ids } let read_packets ~with_extras ack_fn = let messages = collect_messages ~with_extras ack_fn in let packets = group_into_packets messages in Log.debug (fun f -> f "[%s.Reader] read_packets: %d messages -> %d packets (%d dropped)" C.name (List.length messages) (List.length packets) (List.length (List.filter Result.is_error packets))); packets end module RX_Reader = Make_Reader(RX_Channel) module TX_Reader = Make_Reader(TX_Channel) module type IO = sig val read_packets : with_extras:bool -> ack_fn:((Cstruct.t -> unit) -> unit) -> assembled list end module RX_IO : IO = struct let read_packets ~with_extras ~ack_fn = RX_Reader.read_packets ~with_extras ack_fn end module TX_IO : IO = struct let read_packets ~with_extras ~ack_fn = TX_Reader.read_packets ~with_extras ack_fn end