22
33use p4rs:: TableEntry ;
44use std:: collections:: BTreeMap ;
5+ use std:: collections:: BTreeSet ;
56use std:: fs:: OpenOptions ;
67use std:: io:: Read ;
78use std:: io:: Write ;
@@ -207,7 +208,8 @@ enum Commands {
207208 } ,
208209}
209210
210- const ROUTER_V4 : & str = "ingress.router.v4.rtr" ;
211+ const ROUTER_V4_RT : & str = "ingress.router.v4_route.rtr" ;
212+ const ROUTER_V4_IDX : & str = "ingress.router.v4_idx.rtr" ;
211213const ROUTER_V6 : & str = "ingress.router.v6.rtr" ;
212214const LOCAL_V6 : & str = "ingress.local.local_v6" ;
213215const LOCAL_V4 : & str = "ingress.local.local_v4" ;
@@ -218,6 +220,78 @@ const RESOLVER_V6: &str = "ingress.resolver.resolver_v6";
218220const MAC_REWRITE : & str = "ingress.mac.mac_rewrite" ;
219221const PROXY_ARP : & str = "ingress.pxarp.proxy_arp" ;
220222
223+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
224+ struct Ipv4Cidr {
225+ address : Ipv4Addr ,
226+ mask : u8 ,
227+ }
228+
229+ struct V4RouteData {
230+ data : BTreeMap < Ipv4Cidr , u16 > ,
231+ }
232+
233+ impl V4RouteData {
234+ // Given the full set of tables, extract the data from the route->idx table
235+ fn extract_route_idx (
236+ full : & BTreeMap < String , Vec < TableEntry > > ,
237+ ) -> BTreeMap < Ipv4Cidr , u16 > {
238+ let mut table = BTreeMap :: new ( ) ;
239+ for e in full. get ( ROUTER_V4_IDX ) . unwrap ( ) {
240+ let subnet = match get_addr_subnet ( & e. keyset_data ) {
241+ Some ( ( IpAddr :: V4 ( address) , mask) ) => Ipv4Cidr { address, mask } ,
242+ _ => continue ,
243+ } ;
244+ let idx = match get_v4_idx_mask ( & e. parameter_data ) {
245+ Some ( ( idx, _mask) ) => idx,
246+ None => continue ,
247+ } ;
248+ table. insert ( subnet, idx) ;
249+ }
250+ table
251+ }
252+
253+ // Fetch the v4 route->idx table
254+ pub async fn load ( cli : & Cli ) -> Self {
255+ let data = match cli. mode {
256+ Mode :: Standalone => {
257+ let ( tx, rx) = std:: sync:: mpsc:: channel ( ) ;
258+ let uds = bind_uds ( cli) ;
259+ let j = tokio:: spawn ( async move {
260+ if let ManagementResponse :: DumpResponse ( ref tables) =
261+ recv_uds ( uds) . await
262+ {
263+ let table = V4RouteData :: extract_route_idx ( tables) ;
264+ tx. send ( table) . unwrap ( ) ;
265+ }
266+ } ) ;
267+ send ( ManagementRequest :: DumpRequest , cli) . await ;
268+ j. await . unwrap ( ) ;
269+ rx. recv ( ) . unwrap ( ) . clone ( )
270+ }
271+ Mode :: Propolis => {
272+ V4RouteData :: extract_route_idx ( & dump_tables_propolis ( ) )
273+ }
274+ } ;
275+ Self { data }
276+ }
277+
278+ // Given a route destination, find the corresponding table index
279+ pub fn lookup ( & self , address : Ipv4Addr , mask : u8 ) -> Option < u16 > {
280+ let cidr = Ipv4Cidr { address, mask } ;
281+ self . data . get ( & cidr) . copied ( )
282+ }
283+
284+ // Find an open slot in the route table
285+ pub fn find_available ( & self ) -> u16 {
286+ let used: BTreeSet < u16 > = self . data . values ( ) . cloned ( ) . collect ( ) ;
287+ let mut i = 0 ;
288+ while used. contains ( & i) {
289+ i += 1 ;
290+ }
291+ i
292+ }
293+ }
294+
221295#[ tokio:: main]
222296async fn main ( ) {
223297 let cli = Cli :: parse ( ) ;
@@ -229,8 +303,11 @@ async fn main() {
229303 port,
230304 nexthop,
231305 } => {
232- let mut keyset_data: Vec < u8 > = destination. octets ( ) . into ( ) ;
233- keyset_data. push ( mask) ;
306+ let table = V4RouteData :: load ( & cli) . await ;
307+ let idx = table. find_available ( ) ;
308+
309+ // Add idx->route to the route table
310+ let keyset_data: Vec < u8 > = idx. to_le_bytes ( ) . to_vec ( ) ;
234311
235312 let mut parameter_data = port. to_le_bytes ( ) . to_vec ( ) ;
236313 let mut nexthop_data: Vec < u8 > = nexthop. octets ( ) . into ( ) ;
@@ -239,28 +316,63 @@ async fn main() {
239316
240317 send (
241318 ManagementRequest :: TableAdd ( TableAdd {
242- table : ROUTER_V4 . into ( ) ,
319+ table : ROUTER_V4_RT . into ( ) ,
243320 action : "forward" . into ( ) ,
244321 keyset_data,
245322 parameter_data,
246323 } ) ,
247324 & cli,
248325 )
249326 . await ;
250- }
251- Commands :: RemoveRoute4 { destination , mask } => {
327+
328+ // Now add cidr->idx to the index table
252329 let mut keyset_data: Vec < u8 > = destination. octets ( ) . into ( ) ;
253330 keyset_data. push ( mask) ;
254331
332+ let mut parameter_data = idx. to_le_bytes ( ) . to_vec ( ) ;
333+ // Hardcode a mask of 0
334+ parameter_data. extend_from_slice ( & 0u16 . to_le_bytes ( ) ) ;
335+
255336 send (
256- ManagementRequest :: TableRemove ( TableRemove {
257- table : ROUTER_V4 . into ( ) ,
337+ ManagementRequest :: TableAdd ( TableAdd {
338+ table : ROUTER_V4_IDX . into ( ) ,
339+ action : "index" . into ( ) ,
258340 keyset_data,
341+ parameter_data,
259342 } ) ,
260343 & cli,
261344 )
262345 . await ;
263346 }
347+ Commands :: RemoveRoute4 { destination, mask } => {
348+ let table = V4RouteData :: load ( & cli) . await ;
349+ if let Some ( idx) = table. lookup ( destination, mask) {
350+ // Remove the entry from the subnet->idx table
351+ let mut keyset_data: Vec < u8 > = destination. octets ( ) . into ( ) ;
352+ keyset_data. push ( mask) ;
353+
354+ send (
355+ ManagementRequest :: TableRemove ( TableRemove {
356+ table : ROUTER_V4_IDX . into ( ) ,
357+ keyset_data,
358+ } ) ,
359+ & cli,
360+ )
361+ . await ;
362+
363+ // Remove the entry from the idx->route table table
364+ let mut keyset_data: Vec < u8 > = idx. to_le_bytes ( ) . to_vec ( ) ;
365+ keyset_data. push ( mask) ;
366+ send (
367+ ManagementRequest :: TableRemove ( TableRemove {
368+ table : ROUTER_V4_RT . into ( ) ,
369+ keyset_data,
370+ } ) ,
371+ & cli,
372+ )
373+ . await ;
374+ }
375+ }
264376
265377 Commands :: AddRoute6 {
266378 destination,
@@ -724,17 +836,29 @@ fn dump_tables(table: &BTreeMap<String, Vec<TableEntry>>) {
724836 } ;
725837 println ! ( "{tgt} -> {gw}" ) ;
726838 }
727- println ! ( "router v4 :" ) ;
728- for e in table. get ( ROUTER_V4 ) . unwrap ( ) {
839+ println ! ( "router v4_idx :" ) ;
840+ for e in table. get ( ROUTER_V4_IDX ) . unwrap ( ) {
729841 let tgt = match get_addr_subnet ( & e. keyset_data ) {
730842 Some ( ( a, m) ) => format ! ( "{a}/{m}" ) ,
731843 None => "?" . into ( ) ,
732844 } ;
845+ let idx = match get_v4_idx_mask ( & e. parameter_data ) {
846+ Some ( ( idx, _mask) ) => format ! ( "{idx}" ) ,
847+ None => "?" . into ( ) ,
848+ } ;
849+ println ! ( "{tgt} -> {idx}" ) ;
850+ }
851+ println ! ( "router v4_routes:" ) ;
852+ for e in table. get ( ROUTER_V4_RT ) . unwrap ( ) {
853+ let idx = match get_u16 ( & e. keyset_data ) {
854+ Some ( idx) => format ! ( "{idx}" ) ,
855+ None => "?" . into ( ) ,
856+ } ;
733857 let gw = match get_port_addr ( & e. parameter_data , false ) {
734858 Some ( ( a, p) ) => format ! ( "{a} ({p})" ) ,
735859 None => "?" . into ( ) ,
736860 } ;
737- println ! ( "{tgt } -> {gw}" ) ;
861+ println ! ( "{idx } -> {gw}" ) ;
738862 }
739863
740864 println ! ( "resolver v4:" ) ;
@@ -875,6 +999,16 @@ fn get_addr(data: &[u8], rev: bool) -> Option<IpAddr> {
875999 }
8761000}
8771001
1002+ fn get_u16 ( data : & [ u8 ] ) -> Option < u16 > {
1003+ match data. len ( ) {
1004+ 2 => Some ( u16:: from_le_bytes ( [ data[ 0 ] , data[ 1 ] ] ) ) ,
1005+ _ => {
1006+ println ! ( "expected u16, found: {data:x?}" ) ;
1007+ None
1008+ }
1009+ }
1010+ }
1011+
8781012fn get_mac ( data : & [ u8 ] ) -> Option < [ u8 ; 6 ] > {
8791013 match data. len ( ) {
8801014 6 => Some ( data. try_into ( ) . unwrap ( ) ) ,
@@ -885,6 +1019,19 @@ fn get_mac(data: &[u8]) -> Option<[u8; 6]> {
8851019 }
8861020}
8871021
1022+ fn get_v4_idx_mask ( data : & [ u8 ] ) -> Option < ( u16 , u16 ) > {
1023+ match data. len ( ) {
1024+ 4 => Some ( (
1025+ u16:: from_le_bytes ( [ data[ 0 ] , data[ 1 ] ] ) ,
1026+ u16:: from_le_bytes ( [ data[ 2 ] , data[ 3 ] ] ) ,
1027+ ) ) ,
1028+ _ => {
1029+ println ! ( "expected [index, mask], found: {data:x?}" ) ;
1030+ None
1031+ }
1032+ }
1033+ }
1034+
8881035fn get_addr_subnet ( data : & [ u8 ] ) -> Option < ( IpAddr , u8 ) > {
8891036 match data. len ( ) {
8901037 5 => Some ( ( get_addr ( & data[ ..4 ] , true ) ?, data[ 4 ] ) ) ,
0 commit comments