APID-based virtual switch for SpaceOS inter-guest routing
at main 136 lines 5.0 kB view raw
1open Space_wire 2 3let log_src = Logs.Src.create "space-net.switch" 4 5module Log = (val Logs.src_log log_src) 6 7type t = { 8 config : Config.t; 9 router : Router.t; 10 on_system : Msg.t -> Msg.t option; 11 on_uplink : Msg.t -> unit; 12 connections : (string, Connection.t) Hashtbl.t; 13 mutable running : bool; 14} 15 16let pp fmt t = 17 Fmt.pf fmt "Switch(%d tenants, %s)" 18 (List.length t.config.tenants) 19 t.config.socket_dir 20 21let default_on_system frame = 22 Log.info (fun m -> m "system frame: %a" Msg.pp frame); 23 None 24 25let default_on_uplink frame = 26 Log.info (fun m -> m "uplink frame: %a" Msg.pp frame) 27 28let v ~config ?(on_system = default_on_system) ?(on_uplink = default_on_uplink) 29 () = 30 let router = Router.of_config config in 31 { 32 config; 33 router; 34 on_system; 35 on_uplink; 36 connections = Hashtbl.create 16; 37 running = true; 38 } 39 40let connection t (tenant : Config.tenant) = 41 Hashtbl.find_opt t.connections tenant.name 42 43(** In v0, the frame carries a source APID in [frame.apid] and a destination 44 APID in [frame.reserved]. The source APID identifies the sending 45 application; the destination APID determines routing. *) 46 47let dest_apid (frame : Msg.t) = frame.reserved 48 49let handle_frame t conn frame = 50 let (frame : Msg.t) = frame in 51 let source = Connection.tenant conn in 52 (* 1. Validate version *) 53 if frame.version <> 0x01 then 54 Connection.write_error conn Unknown_version ~offending:frame 55 else 56 (* 2. Validate msg_type *) 57 match Msg.kind_of_int frame.msg_type with 58 | None -> Connection.write_error conn Unknown_type ~offending:frame 59 | Some _ -> ( 60 (* 3. Validate source APID ownership *) 61 match Router.validate_source t.router ~source ~apid:frame.apid with 62 | Some (Apid_not_allocated _) -> 63 Connection.write_error conn Apid_not_allocated ~offending:frame 64 | Some (Policy_denied _) -> 65 Connection.write_error conn Apid_not_allocated ~offending:frame 66 | None -> ( 67 (* 4. Route by destination APID (carried in reserved field) *) 68 let dest = dest_apid frame in 69 match Router.route t.router ~source ~dest_apid:dest with 70 | Error (Policy_denied { src_tenant; dest_apid }) -> 71 Log.warn (fun m -> 72 m "policy denied: %s -> APID %d" src_tenant dest_apid); 73 Connection.write_error conn Apid_not_allocated ~offending:frame 74 | Error (Apid_not_allocated { apid; tenant }) -> 75 Log.warn (fun m -> m "APID %d not allocated to %s" apid tenant); 76 Connection.write_error conn Apid_not_allocated ~offending:frame 77 | Ok (Router.Local dest_tenant) -> ( 78 match connection t dest_tenant with 79 | Some dest_conn -> Connection.write_frame dest_conn frame 80 | None -> 81 Log.warn (fun m -> 82 m "dest %s not connected" dest_tenant.Config.name)) 83 | Ok Router.System -> ( 84 match t.on_system frame with 85 | Some response -> Connection.write_frame conn response 86 | None -> ()) 87 | Ok Router.Uplink -> t.on_uplink frame 88 | Ok (Router.Drop reason) -> 89 Log.debug (fun m -> m "drop: %s (apid=%d)" reason dest))) 90 91let inject t frame = 92 let dest = dest_apid frame in 93 match Router.lookup t.router dest with 94 | Router.Local tenant -> ( 95 match connection t tenant with 96 | Some conn -> Connection.write_frame conn frame 97 | None -> 98 Log.warn (fun m -> 99 m "inject: dest %s not connected" tenant.Config.name)) 100 | Router.System -> ( match t.on_system frame with Some _ -> () | None -> ()) 101 | Router.Uplink -> t.on_uplink frame 102 | Router.Drop reason -> 103 Log.debug (fun m -> m "inject drop: %s (apid=%d)" reason dest) 104 105let stop t = t.running <- false 106 107let run t ~sw ~net = 108 (try Unix.mkdir t.config.socket_dir 0o755 109 with Unix.Unix_error (Unix.EEXIST, _, _) -> ()); 110 List.iter 111 (fun (tenant : Config.tenant) -> 112 let path = Config.socket_path t.config tenant in 113 Log.info (fun m -> m "listening for %s on %s" tenant.name path); 114 Connection.listen ~sw ~net ~path ~tenant 115 ~on_connect:(fun conn -> Hashtbl.replace t.connections tenant.name conn) 116 ~on_frame:(fun conn frame -> handle_frame t conn frame) 117 ~on_disconnect:(fun _conn -> 118 Log.info (fun m -> m "guest %s disconnected" tenant.name); 119 Hashtbl.remove t.connections tenant.name)) 120 t.config.tenants 121 122let space_packet_uplink ~seq_count frame = 123 let apid = frame.Msg.apid in 124 if apid < 0 || apid > Space_packet.apid_max then None 125 else 126 let data = 127 let buf = Bytes.make Msg.frame_size '\x00' in 128 Wire.Codec.encode Msg.codec frame buf 0; 129 Bytes.unsafe_to_string buf 130 in 131 match 132 Space_packet.v ~packet_type:Telemetry ~apid ~sequence_flags:Unsegmented 133 ~sequence_count:(seq_count ()) data 134 with 135 | Ok pkt -> Some pkt 136 | Error _ -> None