1use std::collections::{HashMap, HashSet};
9use std::fmt::Debug;
10use std::hash::{Hash, Hasher};
11use std::num::{NonZeroU32, NonZeroU64};
12
13use fidl::endpoints::ProtocolMarker;
14use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
15use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
16use fidl_fuchsia_net_resources as fnet_resources;
17use fidl_fuchsia_net_root as fnet_root;
18use fidl_fuchsia_net_routes as fnet_routes;
19use fidl_fuchsia_net_routes_admin::RouteSetError;
20use fidl_fuchsia_net_routes_ext as fnet_routes_ext;
21
22use derivative::Derivative;
23use futures::StreamExt as _;
24use futures::channel::oneshot;
25use linux_uapi::{
26 rt_class_t_RT_TABLE_COMPAT, rt_class_t_RT_TABLE_MAIN, rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
27 rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
28};
29use net_types::ip::{GenericOverIp, Ip, IpAddress, IpVersion, Subnet};
30use net_types::{SpecifiedAddr, SpecifiedAddress, Witness as _};
31use netlink_packet_core::{NLM_F_MULTIPART, NetlinkMessage};
32use netlink_packet_route::route::{
33 RouteAddress, RouteAttribute, RouteHeader, RouteMessage, RouteProtocol, RouteScope, RouteType,
34};
35use netlink_packet_route::{AddressFamily, RouteNetlinkMessage};
36use netlink_packet_utils::DecodeError;
37use netlink_packet_utils::nla::Nla;
38
39use crate::client::{ClientTable, InternalClient};
40use crate::logging::{log_debug, log_error, log_info, log_warn};
41use crate::messaging::Sender;
42use crate::multicast_groups::ModernGroup;
43use crate::netlink_packet::UNSPECIFIED_SEQUENCE_NUMBER;
44use crate::netlink_packet::errno::Errno;
45use crate::protocol_family::ProtocolFamily;
46use crate::protocol_family::route::NetlinkRoute;
47use crate::route_tables::{
48 FidlRouteMap, ManagedRouteTable, NetlinkRouteTableIndex, NonZeroNetlinkRouteTableIndex,
49 RouteRemoveResult, RouteTable, RouteTableMap, TableNeedsCleanup, UnmanagedTable,
50};
51use crate::util::respond_to_completer;
52
53const MAIN_ROUTE_TABLE: u32 = rt_class_t_RT_TABLE_MAIN;
54pub(crate) const MAIN_ROUTE_TABLE_INDEX: NetlinkRouteTableIndex =
55 NetlinkRouteTableIndex::new(MAIN_ROUTE_TABLE);
56
57#[derive(Copy, Clone, Debug, PartialEq, Eq)]
59pub(crate) enum GetRouteArgs {
60 Dump,
61}
62
63#[derive(Copy, Clone, Debug, PartialEq, Eq, GenericOverIp)]
65#[generic_over_ip(I, Ip)]
66pub(crate) struct UnicastNewRouteArgs<I: Ip> {
67 pub subnet: Subnet<I::Addr>,
69 pub target: fnet_routes_ext::RouteTarget<I>,
72 pub priority: Option<NonZeroU32>,
75 pub table: NetlinkRouteTableIndex,
77}
78
79#[derive(Copy, Clone, Debug, PartialEq, Eq)]
81pub(crate) enum NewRouteArgs<I: Ip> {
82 Unicast(UnicastNewRouteArgs<I>),
84}
85
86#[derive(Copy, Clone, Debug, PartialEq, Eq, GenericOverIp)]
89#[generic_over_ip(I, Ip)]
90pub(crate) struct UnicastDelRouteArgs<I: Ip> {
91 pub(crate) subnet: Subnet<I::Addr>,
93 pub(crate) outbound_interface: Option<NonZeroU64>,
95 pub(crate) next_hop: Option<SpecifiedAddr<I::Addr>>,
97 pub(crate) priority: Option<NonZeroU32>,
99 pub(crate) table: NonZeroNetlinkRouteTableIndex,
101}
102
103#[derive(Copy, Clone, Debug, PartialEq, Eq)]
105pub(crate) enum DelRouteArgs<I: Ip> {
106 Unicast(UnicastDelRouteArgs<I>),
108}
109
110#[derive(Copy, Clone, Debug, PartialEq, Eq)]
112pub(crate) enum RouteRequestArgs<I: Ip> {
113 Get(GetRouteArgs),
115 New(NewRouteArgs<I>),
117 Del(DelRouteArgs<I>),
119}
120
121#[derive(Copy, Clone, Debug, PartialEq, Eq)]
123pub(crate) enum RequestArgs<I: Ip> {
124 Route(RouteRequestArgs<I>),
125}
126
127#[derive(Copy, Clone, Debug, PartialEq, Eq)]
129pub(crate) enum RequestError {
130 AlreadyExists,
132 DeletionNotAllowed,
135 InvalidRequest,
137 NotFound,
139 UnrecognizedInterface,
141 Unknown,
143}
144
145impl RequestError {
146 pub(crate) fn into_errno(self) -> Errno {
147 match self {
148 RequestError::AlreadyExists => Errno::EEXIST,
149 RequestError::InvalidRequest => Errno::EINVAL,
150 RequestError::NotFound => Errno::ESRCH,
151 RequestError::DeletionNotAllowed | RequestError::Unknown => Errno::ENOTSUP,
152 RequestError::UnrecognizedInterface => Errno::ENODEV,
153 }
154 }
155}
156
157fn map_route_set_error<I: Ip + fnet_routes_ext::FidlRouteIpExt>(
158 e: RouteSetError,
159 route: &I::Route,
160 interface_id: u64,
161) -> RequestError {
162 match e {
163 RouteSetError::Unauthenticated => {
164 panic!(
168 "authenticated for interface {:?}, but received unauthentication error from route set for route ({:?})",
169 interface_id, route,
170 );
171 }
172 RouteSetError::InvalidDestinationSubnet => {
173 log_debug!(
175 "invalid subnet observed from route ({:?}) from interface {:?}",
176 route,
177 interface_id,
178 );
179 return RequestError::InvalidRequest;
180 }
181 RouteSetError::InvalidNextHop => {
182 log_debug!(
184 "invalid next hop observed from route ({:?}) from interface {:?}",
185 route,
186 interface_id,
187 );
188 return RequestError::InvalidRequest;
189 }
190 err => {
191 log_error!(
197 "unrecognized route set error {:?} with route ({:?}) from interface {:?}",
198 err,
199 route,
200 interface_id
201 );
202 return RequestError::Unknown;
203 }
204 }
205}
206
207#[derive(Derivative, GenericOverIp)]
209#[derivative(Debug(bound = ""))]
210#[generic_over_ip(I, Ip)]
211pub(crate) struct Request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>, I: Ip> {
212 pub args: RequestArgs<I>,
214 pub sequence_number: u32,
219 pub client: InternalClient<NetlinkRoute, S>,
221 pub completer: oneshot::Sender<Result<(), RequestError>>,
223}
224
225#[derive(GenericOverIp)]
229#[generic_over_ip(I, Ip)]
230pub(crate) struct RoutesWorker<
231 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
232> {
233 fidl_route_map: FidlRouteMap<I>,
234 stashed_routes: HashMap<fnet_routes_ext::TableId, HashSet<fnet_routes_ext::InstalledRoute<I>>>,
237}
238
239#[cfg(test)]
240impl<I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt> Drop
241 for RoutesWorker<I>
242{
243 fn drop(&mut self) {
244 assert!(self.stashed_routes.is_empty(), "the stashed routes must be eventually reconciled");
245 }
246}
247
248fn get_table_u8_and_nla_from_key(
249 netlink_id: NetlinkRouteTableIndex,
250) -> (u8, Option<RouteAttribute>) {
251 let table_id = netlink_id.get();
252 match u8::try_from(table_id) {
255 Ok(t) => (t, None),
256 Err(_) => (rt_class_t_RT_TABLE_COMPAT as u8, Some(RouteAttribute::Table(table_id))),
258 }
259}
260
261#[derive(Clone, Debug, PartialEq, Eq)]
263pub(crate) enum PendingRouteRequestArgs<I: Ip> {
264 New(NewRouteArgs<I>),
266 Del((NetlinkRouteMessage, NonZeroNetlinkRouteTableIndex)),
268}
269
270#[derive(Derivative)]
271#[derivative(Debug(bound = ""))]
272pub(crate) struct PendingRouteRequest<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>, I: Ip>
273{
274 request_args: PendingRouteRequestArgs<I>,
275 client: InternalClient<NetlinkRoute, S>,
276 completer: oneshot::Sender<Result<(), RequestError>>,
277}
278
279impl<I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt>
280 RoutesWorker<I>
281{
282 pub(crate) async fn create(
289 main_route_table: &<I::RouteTableMarker as ProtocolMarker>::Proxy,
290 routes_state_proxy: &<I::StateMarker as ProtocolMarker>::Proxy,
291 route_table_provider: <I::RouteTableProviderMarker as ProtocolMarker>::Proxy,
292 ) -> (
293 Self,
294 RouteTableMap<I>,
295 impl futures::Stream<Item = Result<fnet_routes_ext::Event<I>, fnet_routes_ext::WatchError>>
296 + Unpin
297 + 'static,
298 ) {
299 let mut route_event_stream = Box::pin(
300 fnet_routes_ext::event_stream_from_state(routes_state_proxy)
301 .expect("connecting to fuchsia.net.routes.State FIDL should succeed"),
302 );
303 let installed_routes = fnet_routes_ext::collect_routes_until_idle::<_, HashSet<_>>(
304 route_event_stream.by_ref(),
305 )
306 .await
307 .expect("determining already installed routes should succeed");
308
309 let mut fidl_route_map = FidlRouteMap::<I>::default();
310 for fnet_routes_ext::InstalledRoute { route, effective_properties, table_id } in
311 installed_routes
312 {
313 let _: Option<fnet_routes_ext::EffectiveRouteProperties> =
314 fidl_route_map.add(route, table_id, effective_properties);
315 }
316
317 let main_route_table_id = fnet_routes_ext::admin::get_table_id::<I>(main_route_table)
318 .await
319 .expect("getting main route table ID should succeed");
320 let unmanaged_route_set_proxy =
321 fnet_routes_ext::admin::new_route_set::<I>(main_route_table)
322 .expect("getting unmanaged route set should succeed");
323 let route_table_map = RouteTableMap::new(
324 main_route_table.clone(),
325 main_route_table_id,
326 unmanaged_route_set_proxy,
327 route_table_provider,
328 );
329 (
330 Self { fidl_route_map, stashed_routes: HashMap::new() },
331 route_table_map,
332 route_event_stream,
333 )
334 }
335
336 pub(crate) fn handle_route_watcher_event<
344 S: Sender<<NetlinkRoute as ProtocolFamily>::Response>,
345 >(
346 &mut self,
347 route_table_map: &mut RouteTableMap<I>,
348 route_clients: &ClientTable<NetlinkRoute, S>,
349 event: fnet_routes_ext::Event<I>,
350 ) -> Option<TableNeedsCleanup> {
351 let res = handle_route_watcher_event::<I, S>(
352 route_table_map,
353 &mut self.fidl_route_map,
354 route_clients,
355 event,
356 );
357 match res {
358 RouteEventOutcome::Noop => None,
359 RouteEventOutcome::Cleanup(cleanup) => Some(cleanup),
360 RouteEventOutcome::UpdateStash(event) => {
361 match event {
362 AddOrRemoveRoute::Added(r) => {
363 assert!(
364 self.stashed_routes.entry(r.table_id).or_default().insert(r),
365 "route {:?} already stashed",
366 r,
367 )
368 }
369 AddOrRemoveRoute::Removed(r) => {
370 match self.stashed_routes.get_mut(&r.table_id) {
371 Some(table) => {
372 assert!(table.remove(&r), "route {:?} not stashed", r,);
373 }
374 None => {
375 log_info!(
376 "netlink route table for {:?} already removed",
377 r.table_id
378 );
379 }
380 }
381 }
382 }
383 None
384 }
385 }
386 }
387
388 pub(crate) fn process_stashed_routes<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
392 &mut self,
393 route_table_map: &mut RouteTableMap<I>,
394 route_clients: &ClientTable<NetlinkRoute, S>,
395 netlink_table_id: NetlinkRouteTableIndex,
396 ) {
397 let fidl_table_id = route_table_map
398 .get_mut(&netlink_table_id)
399 .expect("invalid netlink table id")
400 .fidl_table_id();
401
402 if let Some(stashed_routes) = self.stashed_routes.remove(&fidl_table_id) {
403 log_info!(
404 "processing {} stashed route events for table {}",
405 stashed_routes.len(),
406 fidl_table_id
407 );
408 for route in stashed_routes {
409 assert_eq!(
410 handle_route_watcher_event::<I, S>(
411 route_table_map,
412 &mut self.fidl_route_map,
413 route_clients,
414 fnet_routes_ext::Event::Added(route),
415 ),
416 RouteEventOutcome::Noop,
417 "adding routes should not have clean ups"
418 );
419 }
420 }
421 }
422
423 fn get_interface_control(
424 interfaces_proxy: &fnet_root::InterfacesProxy,
425 interface_id: u64,
426 ) -> fnet_interfaces_ext::admin::Control {
427 let (control, server_end) =
428 fidl::endpoints::create_proxy::<fnet_interfaces_admin::ControlMarker>();
429 interfaces_proxy.get_admin(interface_id, server_end).expect("send get admin request");
430 fnet_interfaces_ext::admin::Control::new(control)
431 }
432
433 async fn authenticate_for_interface(
434 interfaces_proxy: &fnet_root::InterfacesProxy,
435 route_set_proxy: &<I::RouteSetMarker as fidl::endpoints::ProtocolMarker>::Proxy,
436 interface_id: u64,
437 ) -> Result<(), RequestError> {
438 let control = Self::get_interface_control(interfaces_proxy, interface_id);
439
440 let grant = match control.get_authorization_for_interface().await {
441 Ok(grant) => grant,
442 Err(fnet_interfaces_ext::admin::TerminalError::Fidl(
443 fidl::Error::ClientChannelClosed { epitaph, protocol_name, .. },
444 )) => {
445 log_debug!(
446 "{epitaph:?}: netstack dropped the {protocol_name} channel, \
447 interface {interface_id} does not exist"
448 );
449 return Err(RequestError::UnrecognizedInterface);
450 }
451 Err(e) => panic!("unexpected error from interface authorization request: {e:?}"),
452 };
453 let proof = fnet_interfaces_ext::admin::proof_from_grant(&grant);
454
455 #[derive(GenericOverIp)]
456 #[generic_over_ip(I, Ip)]
457 struct AuthorizeInputs<'a, I: fnet_routes_ext::admin::FidlRouteAdminIpExt> {
458 route_set_proxy: &'a <I::RouteSetMarker as fidl::endpoints::ProtocolMarker>::Proxy,
459 proof: fnet_resources::ProofOfInterfaceAuthorization,
460 }
461
462 let authorize_fut = I::map_ip_in(
463 AuthorizeInputs::<'_, I> { route_set_proxy, proof },
464 |AuthorizeInputs { route_set_proxy, proof }| {
465 route_set_proxy.authenticate_for_interface(proof)
466 },
467 |AuthorizeInputs { route_set_proxy, proof }| {
468 route_set_proxy.authenticate_for_interface(proof)
469 },
470 );
471
472 authorize_fut.await.expect("sent authorization request").map_err(|e| {
473 log_warn!("error authenticating for interface ({interface_id}): {e:?}");
474 RequestError::UnrecognizedInterface
475 })?;
476
477 Ok(())
478 }
479
480 async fn handle_new_route_request(
486 &self,
487 route_tables: &mut RouteTableMap<I>,
488 interfaces_proxy: &fnet_root::InterfacesProxy,
489 args: NewRouteArgs<I>,
490 ) -> Result<NewRouteArgs<I>, RequestError> {
491 let (interface_id, table) = match args {
492 NewRouteArgs::Unicast(args) => (args.target.outbound_interface, args.table),
493 };
494 let route: fnet_routes_ext::Route<I> = args.into();
495
496 route_tables.create_managed_route_table_if_not_present(table).await;
500
501 let table_id = route_tables.get(&table).expect("should be populated").fidl_table_id();
502
503 let new_route_conflicts_with_existing = self
511 .fidl_route_map
512 .iter_table(route_tables.get(&table).expect("should be populated").fidl_table_id())
513 .any(|(stored_route, stored_props)| {
514 routes_conflict::<I>(
515 fnet_routes_ext::InstalledRoute {
516 route: *stored_route,
517 effective_properties: *stored_props,
518 table_id,
519 },
520 route,
521 table_id,
522 )
523 });
524
525 if new_route_conflicts_with_existing {
526 return Err(RequestError::AlreadyExists);
527 }
528
529 let route_set = match route_tables.get(&table).expect("should have just been populated") {
530 RouteTable::Managed(ManagedRouteTable { route_set_proxy, .. }) => route_set_proxy,
531 RouteTable::Unmanaged(UnmanagedTable { route_set_proxy, .. }) => route_set_proxy,
532 };
533
534 let route: I::Route =
535 route.try_into().expect("should not have constructed unknown route action");
536 let added_to_table: bool = Self::dispatch_route_proxy_fn(
537 &route,
538 interface_id,
539 &interfaces_proxy,
540 route_set,
541 fnet_routes_ext::admin::add_route::<I>,
542 )
543 .await?;
544
545 if !added_to_table {
548 return Err(RequestError::AlreadyExists);
549 };
550
551 Ok(args)
552 }
553
554 async fn handle_del_route_request(
560 &self,
561 interfaces_proxy: &fnet_root::InterfacesProxy,
562 route_tables: &mut RouteTableMap<I>,
563 del_route_args: DelRouteArgs<I>,
564 ) -> Result<(NetlinkRouteMessage, NonZeroNetlinkRouteTableIndex), RequestError> {
565 let table = match del_route_args {
566 DelRouteArgs::Unicast(args) => args.table,
567 };
568
569 let route_to_delete = &self
570 .select_route_for_deletion(route_tables, del_route_args)
571 .ok_or(RequestError::NotFound)?;
572 let NetlinkRouteMessage(route) = route_to_delete;
573 let interface_id = route
574 .attributes
575 .iter()
576 .filter_map(|nla| match nla {
577 RouteAttribute::Oif(interface) => Some(*interface as u64),
578 _nla => None,
579 })
580 .next()
581 .expect("there should be exactly one Oif NLA present");
582
583 let route_set = match route_tables.get(&table.into()) {
584 None => return Err(RequestError::NotFound),
585 Some(lookup) => match lookup {
586 RouteTable::Managed(ManagedRouteTable { route_set_proxy, .. }) => route_set_proxy,
587 RouteTable::Unmanaged(UnmanagedTable { route_set_proxy, .. }) => route_set_proxy,
588 },
589 };
590
591 let route: fnet_routes_ext::Route<I> = route_to_delete.to_owned().into();
592
593 let route: I::Route = route.try_into().expect("route should be converted");
594 let removed: bool = Self::dispatch_route_proxy_fn(
595 &route,
596 interface_id,
597 &interfaces_proxy,
598 route_set,
599 fnet_routes_ext::admin::remove_route::<I>,
600 )
601 .await?;
602
603 if !removed {
604 log_error!(
605 "Route was not removed as a result of this call. Likely Linux wanted \
606 to remove a route from the global route set which is not supported \
607 by this API, route: {:?}",
608 route_to_delete
609 );
610 return Err(RequestError::DeletionNotAllowed);
611 }
612
613 Ok((route_to_delete.to_owned(), table))
614 }
615
616 fn select_route_for_deletion(
626 &self,
627 route_tables: &RouteTableMap<I>,
628 deletion_args: DelRouteArgs<I>,
629 ) -> Option<NetlinkRouteMessage> {
630 select_route_for_deletion(&self.fidl_route_map, route_tables, deletion_args)
631 }
632
633 async fn dispatch_route_proxy_fn<'a, Fut>(
641 route: &'a I::Route,
642 interface_id: u64,
643 interfaces_proxy: &'a fnet_root::InterfacesProxy,
644 route_set_proxy: &'a <I::RouteSetMarker as ProtocolMarker>::Proxy,
645 dispatch_fn: impl Fn(&'a <I::RouteSetMarker as ProtocolMarker>::Proxy, &'a I::Route) -> Fut,
646 ) -> Result<bool, RequestError>
647 where
648 Fut: futures::Future<Output = Result<Result<bool, RouteSetError>, fidl::Error>>,
649 {
650 match dispatch_fn(route_set_proxy, &route).await.expect("sent route proxy request") {
651 Ok(made_change) => return Ok(made_change),
652 Err(RouteSetError::Unauthenticated) => {}
653 Err(e) => {
654 log_warn!("error altering route on interface ({interface_id}): {e:?}");
655 return Err(map_route_set_error::<I>(e, route, interface_id));
656 }
657 };
658
659 Self::authenticate_for_interface(interfaces_proxy, route_set_proxy, interface_id).await?;
662
663 dispatch_fn(route_set_proxy, &route).await.expect("sent route proxy request").map_err(|e| {
667 log_warn!(
668 "error altering route after authenticating for \
669 interface ({interface_id}): {e:?}"
670 );
671 map_route_set_error::<I>(e, route, interface_id)
672 })
673 }
674
675 pub(crate) async fn handle_request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
681 &mut self,
682 route_tables: &mut RouteTableMap<I>,
683 interfaces_proxy: &fnet_root::InterfacesProxy,
684 Request { args, sequence_number, mut client, completer }: Request<S, I>,
685 ) -> Option<PendingRouteRequest<S, I>> {
686 log_debug!("handling request {args:?} from {client}");
687
688 #[derive(Derivative)]
689 #[derivative(Debug(bound = ""))]
690 enum RequestHandled<S, I>
691 where
692 S: Sender<<NetlinkRoute as ProtocolFamily>::Response>,
693 I: Ip,
694 {
695 Pending(PendingRouteRequest<S, I>),
696 Done(
697 Result<(), RequestError>,
698 InternalClient<NetlinkRoute, S>,
699 oneshot::Sender<Result<(), RequestError>>,
700 ),
701 }
702
703 let request_handled = match args {
704 RequestArgs::Route(args) => match args {
705 RouteRequestArgs::Get(args) => match args {
706 GetRouteArgs::Dump => {
707 self.fidl_route_map
708 .iter()
709 .flat_map(|(route, tables)| {
710 tables.iter().map(move |(fidl_table_id, props)| {
711 fnet_routes_ext::InstalledRoute {
712 route: *route,
713 table_id: *fidl_table_id,
714 effective_properties: *props,
715 }
716 })
717 })
718 .filter_map(|installed_route| {
719 let table_index =
720 route_tables.get_netlink_id(&installed_route.table_id)?;
721 NetlinkRouteMessage::optionally_from(installed_route, table_index)
722 })
723 .for_each(|message| {
724 client.send_unicast(
725 message.into_rtnl_new_route(sequence_number, true),
726 )
727 });
728 RequestHandled::Done(Ok(()), client, completer)
729 }
730 },
731 RouteRequestArgs::New(args) => {
732 match self.handle_new_route_request(route_tables, interfaces_proxy, args).await
733 {
734 Ok(args) => {
735 RequestHandled::Pending(PendingRouteRequest {
738 request_args: PendingRouteRequestArgs::New(args),
739 client,
740 completer,
741 })
742 }
743 Err(err) => RequestHandled::Done(Err(err), client, completer),
744 }
745 }
746 RouteRequestArgs::Del(args) => {
747 match self.handle_del_route_request(interfaces_proxy, route_tables, args).await
748 {
749 Ok(del_route) => {
750 RequestHandled::Pending(PendingRouteRequest {
754 request_args: PendingRouteRequestArgs::Del(del_route),
755 client,
756 completer,
757 })
758 }
759 Err(e) => RequestHandled::Done(Err(e), client, completer),
760 }
761 }
762 },
763 };
764
765 match request_handled {
766 RequestHandled::Done(result, client, completer) => {
767 log_debug!("handled request {args:?} from {client} with result = {result:?}");
768
769 respond_to_completer(client, completer, result, args);
770 None
771 }
772 RequestHandled::Pending(pending) => Some(pending),
773 }
774 }
775
776 pub(crate) fn handle_pending_request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
783 &self,
784 route_tables: &mut RouteTableMap<I>,
785 pending_route_request: PendingRouteRequest<S, I>,
786 ) -> Option<PendingRouteRequest<S, I>> {
787 let PendingRouteRequest { request_args, client: _, completer: _ } = &pending_route_request;
788
789 let done = match request_args {
790 PendingRouteRequestArgs::New(args) => {
791 let netlink_table_id = match args {
792 NewRouteArgs::Unicast(args) => &args.table,
793 };
794
795 let own_fidl_table_id = route_tables
796 .get(netlink_table_id)
797 .expect("should recognize table referenced in pending new route request")
798 .fidl_table_id();
799
800 self.fidl_route_map
801 .route_is_installed_in_tables(&(*args).into(), [&own_fidl_table_id])
802 }
803 PendingRouteRequestArgs::Del((route_msg, pending_table)) => {
806 let netlink_table_id: NetlinkRouteTableIndex = (*pending_table).into();
807 let own_fidl_table_id: Option<fnet_routes_ext::TableId> =
810 route_tables.get(&netlink_table_id).map(|table| table.fidl_table_id());
811
812 if let Some(own_fidl_table_id) = own_fidl_table_id {
813 self.fidl_route_map.route_is_uninstalled_in_tables(
814 &route_msg.clone().into(),
815 [&own_fidl_table_id],
816 )
817 } else {
818 true
819 }
820 }
821 };
822
823 if done {
824 log_debug!("completed pending request; req = {pending_route_request:?}");
825 let PendingRouteRequest { request_args, client, completer } = pending_route_request;
826
827 respond_to_completer(client, completer, Ok(()), request_args);
828 None
829 } else {
830 log_debug!("pending request not done yet; req = {pending_route_request:?}");
832 Some(pending_route_request)
833 }
834 }
835
836 pub(crate) fn any_routes_reference_table(
837 &self,
838 TableNeedsCleanup(table_id, _table_index): TableNeedsCleanup,
839 ) -> bool {
840 self.fidl_route_map.table_is_present(table_id)
841 }
842}
843
844fn routes_conflict<I: Ip>(
852 stored_route: fnet_routes_ext::InstalledRoute<I>,
853 incoming_route: fnet_routes_ext::Route<I>,
854 incoming_table: fnet_routes_ext::TableId,
855) -> bool {
856 let fnet_routes_ext::InstalledRoute {
857 route:
858 fnet_routes_ext::Route {
859 destination: stored_destination,
860 action: _,
861 properties: stored_properties,
862 },
863 effective_properties: _,
864 table_id: stored_table_id,
865 } = stored_route;
866
867 let destinations_match = stored_destination == incoming_route.destination;
868 let specified_metrics_match = stored_properties.specified_properties.metric
869 == incoming_route.properties.specified_properties.metric;
870 let tables_match = stored_table_id == incoming_table;
871
872 destinations_match && specified_metrics_match && tables_match
873}
874
875#[derive(Debug, Clone, PartialEq, Eq, Hash)]
877enum AddOrRemoveRoute<I: Ip> {
878 Added(fnet_routes_ext::InstalledRoute<I>),
879 Removed(fnet_routes_ext::InstalledRoute<I>),
880}
881
882#[derive(Debug, Clone, PartialEq, Eq)]
884enum RouteEventOutcome<I: Ip> {
885 Noop,
886 Cleanup(TableNeedsCleanup),
887 UpdateStash(AddOrRemoveRoute<I>),
888}
889
890fn handle_route_watcher_event<
891 I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
892 S: Sender<<NetlinkRoute as ProtocolFamily>::Response>,
893>(
894 route_table_map: &mut RouteTableMap<I>,
895 fidl_route_map: &mut FidlRouteMap<I>,
896 route_clients: &ClientTable<NetlinkRoute, S>,
897 event: fnet_routes_ext::Event<I>,
898) -> RouteEventOutcome<I> {
899 let (table_id, event) = match event {
900 fnet_routes_ext::Event::Added(e) => (e.table_id, AddOrRemoveRoute::Added(e)),
901 fnet_routes_ext::Event::Removed(e) => (e.table_id, AddOrRemoveRoute::Removed(e)),
902 e @ fnet_routes_ext::Event::Existing(_)
903 | e @ fnet_routes_ext::Event::Idle
904 | e @ fnet_routes_ext::Event::Unknown => {
905 panic!("Netstack reported an unexpected route event: {e:?}");
906 }
907 };
908
909 let netlink_id = match route_table_map.get_netlink_id(&table_id) {
910 None => {
911 log_info!(
914 "Observed a route event via the routes watcher that is installed in a \
915 non-main FIDL table currently not managed by netlink: {event:?}."
916 );
917 return RouteEventOutcome::UpdateStash(event);
918 }
919 Some(table) => table,
920 };
921
922 let (message_for_clients, table_no_routes) = match event {
923 AddOrRemoveRoute::Added(added_installed_route) => {
924 let fnet_routes_ext::InstalledRoute { route, table_id, effective_properties } =
925 added_installed_route;
926
927 match fidl_route_map.add(route, table_id, effective_properties) {
928 None => (),
929 Some(_properties) => {
930 panic!(
931 "Netstack reported the addition of an existing route: \
932 route={route:?}, table={table_id:?}"
933 );
934 }
935 }
936
937 (
938 NetlinkRouteMessage::optionally_from(added_installed_route, netlink_id).map(
939 |route_message| {
940 route_message.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false)
941 },
942 ),
943 RouteEventOutcome::Noop,
944 )
945 }
946 AddOrRemoveRoute::Removed(removed_installed_route) => {
947 let fnet_routes_ext::InstalledRoute { route, table_id, effective_properties: _ } =
948 removed_installed_route;
949
950 let need_clean_up_empty_table = match fidl_route_map.remove(route, table_id) {
951 RouteRemoveResult::DidNotExist => {
952 panic!(
953 "Netstack reported the removal of an unknown route: \
954 route={route:?}, table={table_id:?}"
955 );
956 }
957 RouteRemoveResult::RemovedButTableNotEmpty(_properties) => false,
958 RouteRemoveResult::RemovedAndTableNewlyEmpty(_properties) => true,
959 };
960
961 (
962 NetlinkRouteMessage::optionally_from(removed_installed_route, netlink_id)
963 .map(|route_message| route_message.into_rtnl_del_route()),
964 match need_clean_up_empty_table {
965 true => RouteEventOutcome::Cleanup(TableNeedsCleanup(table_id, netlink_id)),
966 false => RouteEventOutcome::Noop,
967 },
968 )
969 }
970 };
971 if let Some(message_for_clients) = message_for_clients {
972 let route_group = match I::VERSION {
973 IpVersion::V4 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
974 IpVersion::V6 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
975 };
976 route_clients.send_message_to_group(message_for_clients, route_group);
977 }
978
979 table_no_routes
980}
981
982#[derive(Clone, Debug, Eq, PartialEq)]
985pub(crate) struct NetlinkRouteMessage(pub(crate) RouteMessage);
986
987impl NetlinkRouteMessage {
988 pub(crate) fn optionally_from<I: Ip>(
992 route: fnet_routes_ext::InstalledRoute<I>,
993 table: NetlinkRouteTableIndex,
994 ) -> Option<NetlinkRouteMessage> {
995 match NetlinkRouteMessage::try_from_installed_route::<I>(route, table) {
996 Ok(route) => Some(route),
997 Err(NetlinkRouteMessageConversionError::RouteActionNotForwarding) => {
998 log_warn!("Unexpected non-forwarding route in routing table: {:?}", route);
999 None
1000 }
1001 Err(NetlinkRouteMessageConversionError::InvalidInterfaceId(id)) => {
1002 log_warn!("Invalid interface id found in routing table route: {:?}", id);
1003 None
1004 }
1005 Err(NetlinkRouteMessageConversionError::FailedToDecode(err)) => {
1006 log_warn!("Unable to decode route address: {:?}", err);
1007 None
1008 }
1009 }
1010 }
1011
1012 pub(crate) fn into_rtnl_new_route(
1014 self,
1015 sequence_number: u32,
1016 is_dump: bool,
1017 ) -> NetlinkMessage<RouteNetlinkMessage> {
1018 let NetlinkRouteMessage(message) = self;
1019 let mut msg: NetlinkMessage<RouteNetlinkMessage> =
1020 RouteNetlinkMessage::NewRoute(message).into();
1021 msg.header.sequence_number = sequence_number;
1022 if is_dump {
1023 msg.header.flags |= NLM_F_MULTIPART;
1024 }
1025 msg.finalize();
1026 msg
1027 }
1028
1029 fn into_rtnl_del_route(self) -> NetlinkMessage<RouteNetlinkMessage> {
1031 let NetlinkRouteMessage(message) = self;
1032 let mut msg: NetlinkMessage<RouteNetlinkMessage> =
1033 RouteNetlinkMessage::DelRoute(message).into();
1034 msg.finalize();
1035 msg
1036 }
1037
1038 fn try_from_installed_route<I: Ip>(
1043 fnet_routes_ext::InstalledRoute {
1044 route: fnet_routes_ext::Route { destination, action, properties: _ },
1045 effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
1046 table_id: _,
1048 }: fnet_routes_ext::InstalledRoute<I>,
1049 table: NetlinkRouteTableIndex,
1050 ) -> Result<Self, NetlinkRouteMessageConversionError> {
1051 let fnet_routes_ext::RouteTarget { outbound_interface, next_hop } = match action {
1052 fnet_routes_ext::RouteAction::Unknown => {
1053 return Err(NetlinkRouteMessageConversionError::RouteActionNotForwarding);
1054 }
1055 fnet_routes_ext::RouteAction::Forward(target) => target,
1056 };
1057
1058 let mut route_header = RouteHeader::default();
1059 route_header.address_family = match I::VERSION {
1062 IpVersion::V4 => AddressFamily::Inet,
1063 IpVersion::V6 => AddressFamily::Inet6,
1064 }
1065 .try_into()
1066 .expect("should fit into u8");
1067 route_header.destination_prefix_length = destination.prefix();
1068
1069 let (table_u8, table_nla) = get_table_u8_and_nla_from_key(table);
1070 route_header.table = table_u8;
1071
1072 route_header.protocol = RouteProtocol::Kernel;
1080 route_header.scope = RouteScope::Universe;
1083 route_header.kind = RouteType::Unicast;
1084
1085 let mut nlas = vec![];
1106
1107 if route_header.destination_prefix_length > 0 {
1110 let destination_nla = RouteAttribute::Destination(RouteAddress::parse(
1111 route_header.address_family,
1112 destination.network().bytes(),
1113 )?);
1114 nlas.push(destination_nla);
1115 }
1116
1117 let outbound_id: u32 = match outbound_interface.try_into() {
1119 Err(std::num::TryFromIntError { .. }) => {
1120 return Err(NetlinkRouteMessageConversionError::InvalidInterfaceId(
1121 outbound_interface,
1122 ));
1123 }
1124 Ok(id) => id,
1125 };
1126 let oif_nla = RouteAttribute::Oif(outbound_id);
1127 nlas.push(oif_nla);
1128
1129 if let Some(next_hop) = next_hop {
1130 let bytes = RouteAddress::parse(route_header.address_family, next_hop.bytes())?;
1131 let gateway_nla = RouteAttribute::Gateway(bytes);
1132 nlas.push(gateway_nla);
1133 }
1134
1135 let priority_nla = RouteAttribute::Priority(metric);
1136 nlas.push(priority_nla);
1137
1138 if let Some(nla) = table_nla {
1140 nlas.push(nla);
1141 }
1142
1143 let mut route_message = RouteMessage::default();
1144 route_message.header = route_header;
1145 route_message.attributes = nlas;
1146 Ok(NetlinkRouteMessage(route_message))
1147 }
1148}
1149
1150impl Hash for NetlinkRouteMessage {
1151 fn hash<H: Hasher>(&self, state: &mut H) {
1152 let NetlinkRouteMessage(message) = self;
1153 message.header.hash(state);
1154
1155 let mut buffer = vec![];
1156 message.attributes.iter().for_each(|nla| {
1157 buffer.resize(nla.value_len(), 0u8);
1158 nla.emit_value(&mut buffer);
1159 buffer.hash(state);
1160 });
1161 }
1162}
1163
1164#[derive(Debug, PartialEq)]
1166pub(crate) enum NetlinkRouteMessageConversionError {
1167 RouteActionNotForwarding,
1169 InvalidInterfaceId(u64),
1171 FailedToDecode(DecodeErrorWrapper),
1173}
1174
1175#[derive(Debug)]
1176pub(crate) struct DecodeErrorWrapper(DecodeError);
1177
1178impl PartialEq for DecodeErrorWrapper {
1179 fn eq(&self, other: &Self) -> bool {
1180 return format!("{:?}", self.0) == format!("{:?}", other.0);
1183 }
1184}
1185
1186impl From<DecodeError> for NetlinkRouteMessageConversionError {
1187 fn from(err: DecodeError) -> Self {
1188 NetlinkRouteMessageConversionError::FailedToDecode(DecodeErrorWrapper(err))
1189 }
1190}
1191
1192pub const DEFAULT_IPV4_ROUTE_PRIORITY: u32 = 0;
1195pub const DEFAULT_IPV6_ROUTE_PRIORITY: u32 = 1024;
1198
1199fn netlink_priority_to_specified_metric(
1200 prio: Option<NonZeroU32>,
1201 v: IpVersion,
1202) -> fnet_routes::SpecifiedMetric {
1203 fnet_routes::SpecifiedMetric::ExplicitMetric(match (prio, v) {
1206 (Some(prio), IpVersion::V4 | IpVersion::V6) => prio.get(),
1207 (None, IpVersion::V4) => DEFAULT_IPV4_ROUTE_PRIORITY,
1208 (None, IpVersion::V6) => DEFAULT_IPV6_ROUTE_PRIORITY,
1209 })
1210}
1211
1212impl<I: Ip> From<NewRouteArgs<I>> for fnet_routes_ext::Route<I> {
1213 fn from(new_route_args: NewRouteArgs<I>) -> Self {
1214 match new_route_args {
1215 NewRouteArgs::Unicast(args) => {
1216 let UnicastNewRouteArgs { subnet, target, priority, table: _ } = args;
1217 let metric = netlink_priority_to_specified_metric(priority, I::VERSION);
1218 fnet_routes_ext::Route {
1219 destination: subnet,
1220 action: fnet_routes_ext::RouteAction::Forward(target),
1221 properties: fnet_routes_ext::RouteProperties {
1222 specified_properties: fnet_routes_ext::SpecifiedRouteProperties { metric },
1223 },
1224 }
1225 }
1226 }
1227 }
1228}
1229
1230impl<I: Ip> From<NetlinkRouteMessage> for fnet_routes_ext::Route<I> {
1235 fn from(netlink_route_message: NetlinkRouteMessage) -> Self {
1236 let NetlinkRouteMessage(route_message) = netlink_route_message;
1237 let RouteNlaView { subnet, metric, interface_id, next_hop } =
1238 view_existing_route_nlas(&route_message);
1239 let subnet = match subnet {
1240 Some(subnet) => crate::netlink_packet::ip_addr_from_route::<I>(&subnet)
1241 .expect("should be valid addr"),
1242 None => I::UNSPECIFIED_ADDRESS,
1243 };
1244
1245 let subnet = Subnet::new(subnet, route_message.header.destination_prefix_length)
1246 .expect("should be valid subnet");
1247
1248 let next_hop = match next_hop {
1249 Some(next_hop) => crate::netlink_packet::ip_addr_from_route::<I>(&next_hop)
1250 .map(SpecifiedAddr::new)
1251 .expect("should be valid addr"),
1252 None => None,
1253 };
1254
1255 fnet_routes_ext::Route {
1256 destination: subnet,
1257 action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget {
1258 outbound_interface: *interface_id as u64,
1259 next_hop,
1260 }),
1261 properties: fnet_routes_ext::RouteProperties {
1262 specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
1263 metric: fnet_routes::SpecifiedMetric::ExplicitMetric(*metric),
1264 },
1265 },
1266 }
1267 }
1268}
1269
1270struct RouteNlaView<'a> {
1272 subnet: Option<&'a RouteAddress>,
1273 metric: &'a u32,
1274 interface_id: &'a u32,
1275 next_hop: Option<&'a RouteAddress>,
1276}
1277
1278fn view_existing_route_nlas(route: &RouteMessage) -> RouteNlaView<'_> {
1292 let mut subnet = None;
1293 let mut metric = None;
1294 let mut interface_id = None;
1295 let mut next_hop = None;
1296 let mut table = None;
1297 route.attributes.iter().for_each(|nla| match nla {
1298 RouteAttribute::Destination(dst) => {
1299 assert_eq!(subnet, None, "existing route has multiple `Destination` NLAs");
1300 subnet = Some(dst)
1301 }
1302 RouteAttribute::Priority(p) => {
1303 assert_eq!(metric, None, "existing route has multiple `Priority` NLAs");
1304 metric = Some(p)
1305 }
1306 RouteAttribute::Oif(interface) => {
1307 assert_eq!(interface_id, None, "existing route has multiple `Oif` NLAs");
1308 interface_id = Some(interface)
1309 }
1310 RouteAttribute::Gateway(gateway) => {
1311 assert_eq!(next_hop, None, "existing route has multiple `Gateway` NLAs");
1312 next_hop = Some(gateway)
1313 }
1314 RouteAttribute::Table(t) => {
1315 assert_eq!(table, None, "existing route has multiple `Table` NLAs");
1316 table = Some(t)
1317 }
1318 nla => panic!("existing route has unexpected NLA: {nla:?}"),
1319 });
1320 if subnet.is_none() {
1321 assert_eq!(
1322 route.header.destination_prefix_length, 0,
1323 "existing route without `Destination` NLA must be a default route"
1324 );
1325 }
1326
1327 RouteNlaView {
1328 subnet,
1329 metric: metric.expect("existing routes must have a `Priority` NLA"),
1330 interface_id: interface_id.expect("existing routes must have an `Oif` NLA"),
1331 next_hop,
1332 }
1333}
1334
1335fn select_route_for_deletion<
1345 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1346>(
1347 fidl_route_map: &FidlRouteMap<I>,
1348 route_tables: &RouteTableMap<I>,
1349 deletion_args: DelRouteArgs<I>,
1350) -> Option<NetlinkRouteMessage> {
1351 fidl_route_map
1353 .iter_messages(
1354 route_tables,
1355 match deletion_args {
1356 DelRouteArgs::Unicast(args) => args.table.into(),
1357 },
1358 )
1359 .filter_map(|route: NetlinkRouteMessage| {
1360 let NetlinkRouteMessage(existing_route) = &route;
1361 let UnicastDelRouteArgs { subnet, outbound_interface, next_hop, priority, table: _ } =
1362 match deletion_args {
1363 DelRouteArgs::Unicast(args) => args,
1364 };
1365 if subnet.prefix() != existing_route.header.destination_prefix_length {
1366 return None;
1367 }
1368 let RouteNlaView {
1369 subnet: existing_subnet,
1370 metric: existing_metric,
1371 interface_id: existing_interface,
1372 next_hop: existing_next_hop,
1373 } = view_existing_route_nlas(existing_route);
1374 let subnet_matches = existing_subnet.map_or_else(
1375 || !subnet.network().is_specified(),
1376 |dst| {
1377 crate::netlink_packet::ip_addr_from_route::<I>(&dst)
1378 .is_ok_and(|dst: I::Addr| dst == subnet.network())
1379 },
1380 );
1381 let metric_matches = priority.map_or(true, |p| p.get() == *existing_metric);
1382 let interface_matches =
1383 outbound_interface.map_or(true, |i| i.get() == (*existing_interface) as u64);
1384 let next_hop_matches = next_hop.map_or(true, |n| {
1385 existing_next_hop.map_or(false, |e| {
1386 crate::netlink_packet::ip_addr_from_route::<I>(&e)
1387 .is_ok_and(|e: I::Addr| e == n.get())
1388 })
1389 });
1390
1391 let existing_metric = *existing_metric;
1392
1393 if subnet_matches && metric_matches && interface_matches && next_hop_matches {
1394 Some((route, existing_metric))
1395 } else {
1396 None
1397 }
1398 })
1399 .min_by(|(_route1, metric1), (_route2, metric2)| metric1.cmp(metric2))
1401 .map(|(route, _metric)| route)
1402}
1403
1404#[cfg(test)]
1405mod tests {
1406 use super::*;
1407
1408 use std::collections::{HashMap, VecDeque};
1409 use std::convert::Infallible as Never;
1410 use std::pin::pin;
1411 use std::sync::atomic::{AtomicU32, Ordering};
1412
1413 use fidl::endpoints::{ControlHandle, RequestStream, ServerEnd};
1414 use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
1415 use fidl_fuchsia_net_routes as fnet_routes;
1416 use fidl_fuchsia_net_routes_admin as fnet_routes_admin;
1417 use fidl_fuchsia_net_routes_ext::Responder as _;
1418 use fidl_fuchsia_net_routes_ext::admin::{RouteSetRequest, RouteTableRequest};
1419
1420 use assert_matches::assert_matches;
1421 use fuchsia_async as fasync;
1422 use futures::channel::mpsc;
1423 use futures::future::{Future, FutureExt as _};
1424 use futures::stream::TryStreamExt as _;
1425 use futures::{SinkExt as _, Stream};
1426 use ip_test_macro::ip_test;
1427 use linux_uapi::rtnetlink_groups_RTNLGRP_LINK;
1428 use net_declare::{net_ip_v4, net_ip_v6, net_subnet_v4, net_subnet_v6};
1429 use net_types::SpecifiedAddr;
1430 use net_types::ip::{GenericOverIp, IpInvariant, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
1431 use netlink_packet_core::NetlinkPayload;
1432 use test_case::test_case;
1433
1434 use crate::client::AsyncWorkItem;
1435 use crate::interfaces::testutil::FakeInterfacesHandler;
1436 use crate::messaging::testutil::{FakeSender, SentMessage};
1437 use crate::route_eventloop::{EventLoopComponent, Optional, Required};
1438
1439 const V4_SUB1: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.0/32");
1440 const V4_SUB2: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.1/32");
1441 const V4_SUB3: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.0/24");
1442 const V4_DFLT: Subnet<Ipv4Addr> = net_subnet_v4!("0.0.0.0/0");
1443 const V4_NEXTHOP1: Ipv4Addr = net_ip_v4!("192.0.2.1");
1444 const V4_NEXTHOP2: Ipv4Addr = net_ip_v4!("192.0.2.2");
1445
1446 const V6_SUB1: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::/128");
1447 const V6_SUB2: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::1/128");
1448 const V6_SUB3: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::/64");
1449 const V6_DFLT: Subnet<Ipv6Addr> = net_subnet_v6!("::/0");
1450 const V6_NEXTHOP1: Ipv6Addr = net_ip_v6!("2001:db8::1");
1451 const V6_NEXTHOP2: Ipv6Addr = net_ip_v6!("2001:db8::2");
1452
1453 const DEV1: u32 = 1;
1454 const DEV2: u32 = 2;
1455
1456 const METRIC1: u32 = 1;
1457 const METRIC2: u32 = 100;
1458 const METRIC3: u32 = 9999;
1459 const TEST_SEQUENCE_NUMBER: u32 = 1234;
1460 const MANAGED_ROUTE_TABLE_ID: u32 = 5678;
1461 const MANAGED_ROUTE_TABLE_INDEX: NetlinkRouteTableIndex =
1462 NetlinkRouteTableIndex::new(MANAGED_ROUTE_TABLE_ID);
1463 const MAIN_FIDL_TABLE_ID: fnet_routes_ext::TableId = fnet_routes_ext::TableId::new(0);
1464 const OTHER_FIDL_TABLE_ID: fnet_routes_ext::TableId = fnet_routes_ext::TableId::new(1);
1465
1466 fn create_installed_route<I: Ip>(
1467 subnet: Subnet<I::Addr>,
1468 next_hop: Option<I::Addr>,
1469 interface_id: u64,
1470 metric: u32,
1471 table_id: fnet_routes_ext::TableId,
1472 ) -> fnet_routes_ext::InstalledRoute<I> {
1473 fnet_routes_ext::InstalledRoute::<I> {
1474 route: fnet_routes_ext::Route {
1475 destination: subnet,
1476 action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget::<I> {
1477 outbound_interface: interface_id,
1478 next_hop: next_hop.map(|next_hop| SpecifiedAddr::new(next_hop)).flatten(),
1479 }),
1480 properties: fnet_routes_ext::RouteProperties {
1481 specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
1482 metric: fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
1483 },
1484 },
1485 },
1486 effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
1487 table_id,
1488 }
1489 }
1490
1491 fn create_netlink_route_message<I: Ip>(
1492 destination_prefix_length: u8,
1493 table: NetlinkRouteTableIndex,
1494 nlas: Vec<RouteAttribute>,
1495 ) -> NetlinkRouteMessage {
1496 let mut route_header = RouteHeader::default();
1497 let address_family = match I::VERSION {
1498 IpVersion::V4 => AddressFamily::Inet,
1499 IpVersion::V6 => AddressFamily::Inet6,
1500 }
1501 .try_into()
1502 .expect("should fit into u8");
1503 route_header.address_family = address_family;
1504 route_header.destination_prefix_length = destination_prefix_length;
1505 route_header.kind = RouteType::Unicast;
1506 route_header.protocol = RouteProtocol::Kernel;
1507
1508 let (table_u8, _) = get_table_u8_and_nla_from_key(table);
1509 route_header.table = table_u8;
1510
1511 let mut route_message = RouteMessage::default();
1512 route_message.header = route_header;
1513 route_message.attributes = nlas;
1514
1515 NetlinkRouteMessage(route_message)
1516 }
1517
1518 fn create_nlas<I: Ip>(
1519 destination: Option<Subnet<I::Addr>>,
1520 next_hop: Option<I::Addr>,
1521 outgoing_interface_id: u32,
1522 metric: u32,
1523 table: Option<u32>,
1524 ) -> Vec<RouteAttribute> {
1525 let mut nlas = vec![];
1526
1527 let family = match I::VERSION {
1528 IpVersion::V4 => AddressFamily::Inet,
1529 IpVersion::V6 => AddressFamily::Inet6,
1530 };
1531
1532 if let Some(destination) = destination {
1533 let destination_nla = RouteAttribute::Destination(
1534 RouteAddress::parse(family, destination.network().bytes()).unwrap(),
1535 );
1536 nlas.push(destination_nla);
1537 }
1538
1539 let oif_nla = RouteAttribute::Oif(outgoing_interface_id);
1540 nlas.push(oif_nla);
1541
1542 if let Some(next_hop) = next_hop {
1543 let bytes = RouteAddress::parse(family, next_hop.bytes()).unwrap();
1544 let gateway_nla = RouteAttribute::Gateway(bytes);
1545 nlas.push(gateway_nla);
1546 }
1547
1548 let priority_nla = RouteAttribute::Priority(metric);
1549 nlas.push(priority_nla);
1550
1551 if let Some(t) = table {
1552 let table_nla = RouteAttribute::Table(t);
1553 nlas.push(table_nla);
1554 }
1555 nlas
1556 }
1557
1558 #[ip_test(I)]
1559 #[test_case(MAIN_ROUTE_TABLE_INDEX, MAIN_FIDL_TABLE_ID)]
1560 #[test_case(MANAGED_ROUTE_TABLE_INDEX, OTHER_FIDL_TABLE_ID)]
1561 #[fuchsia::test]
1562 async fn handles_route_watcher_event<
1563 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1564 >(
1565 netlink_id: NetlinkRouteTableIndex,
1566 fidl_id: fnet_routes_ext::TableId,
1567 ) {
1568 let scope = fasync::Scope::new();
1569 let (subnet, next_hop) =
1570 I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1571 let installed_route1: fnet_routes_ext::InstalledRoute<I> =
1572 create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, fidl_id);
1573 let installed_route2: fnet_routes_ext::InstalledRoute<I> =
1574 create_installed_route(subnet, Some(next_hop), DEV2.into(), METRIC2, fidl_id);
1575
1576 let add_event1 = fnet_routes_ext::Event::Added(installed_route1);
1577 let add_event2 = fnet_routes_ext::Event::Added(installed_route2);
1578 let remove_event = fnet_routes_ext::Event::Removed(installed_route1);
1579
1580 let expected_route_message1: NetlinkRouteMessage =
1581 NetlinkRouteMessage::try_from_installed_route(installed_route1, netlink_id).unwrap();
1582 let expected_route_message2: NetlinkRouteMessage =
1583 NetlinkRouteMessage::try_from_installed_route(installed_route2, netlink_id).unwrap();
1584
1585 let (right_group, wrong_group) = match I::VERSION {
1587 IpVersion::V4 => (
1588 ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1589 ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1590 ),
1591 IpVersion::V6 => (
1592 ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1593 ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1594 ),
1595 };
1596
1597 let (mut right_sink, right_client, async_work_drain_task) =
1598 crate::client::testutil::new_fake_client::<NetlinkRoute>(
1599 crate::client::testutil::CLIENT_ID_1,
1600 [right_group],
1601 );
1602 let _join_handle = scope.spawn(async_work_drain_task);
1603 let (mut wrong_sink, wrong_client, async_work_drain_task) =
1604 crate::client::testutil::new_fake_client::<NetlinkRoute>(
1605 crate::client::testutil::CLIENT_ID_2,
1606 [wrong_group],
1607 );
1608 let _join_handle = scope.spawn(async_work_drain_task);
1609 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1610 route_clients.add_client(right_client);
1611 route_clients.add_client(wrong_client);
1612
1613 let (route_set_proxy, _route_set_server_end) =
1614 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1615 let (route_table_proxy, _route_table_server_end) =
1616 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1617 let (unmanaged_route_set_proxy, _server_end) =
1618 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1619 let (route_table_provider, _server_end) =
1620 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1621
1622 let mut route_table = RouteTableMap::new(
1623 route_table_proxy.clone(),
1624 MAIN_FIDL_TABLE_ID,
1625 unmanaged_route_set_proxy,
1626 route_table_provider,
1627 );
1628 let mut fidl_route_map = FidlRouteMap::<I>::default();
1629
1630 match netlink_id {
1631 MAIN_ROUTE_TABLE_INDEX => {}
1632 MANAGED_ROUTE_TABLE_INDEX => {
1633 route_table.insert(
1634 netlink_id,
1635 RouteTable::Managed(ManagedRouteTable {
1636 route_table_proxy,
1637 route_set_proxy,
1638 fidl_table_id: OTHER_FIDL_TABLE_ID,
1639 rule_set_authenticated: false,
1640 }),
1641 );
1642 }
1643 _ => panic!("unexpected netlink id: {netlink_id:?}"),
1644 }
1645
1646 assert_eq!(fidl_route_map.iter_messages(&route_table, netlink_id).count(), 0);
1647 assert_eq!(&right_sink.take_messages()[..], &[]);
1648 assert_eq!(&wrong_sink.take_messages()[..], &[]);
1649
1650 assert_eq!(
1651 handle_route_watcher_event(
1652 &mut route_table,
1653 &mut fidl_route_map,
1654 &route_clients,
1655 add_event1,
1656 ),
1657 RouteEventOutcome::Noop,
1658 );
1659 assert_eq!(
1660 fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1661 HashSet::from_iter([expected_route_message1.clone()])
1662 );
1663 assert_eq!(
1664 &right_sink.take_messages()[..],
1665 &[SentMessage::multicast(
1666 expected_route_message1
1667 .clone()
1668 .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
1669 right_group
1670 )]
1671 );
1672 assert_eq!(&wrong_sink.take_messages()[..], &[]);
1673
1674 assert_eq!(
1675 fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1676 HashSet::from_iter([expected_route_message1.clone()])
1677 );
1678 assert_eq!(&right_sink.take_messages()[..], &[]);
1679 assert_eq!(&wrong_sink.take_messages()[..], &[]);
1680
1681 assert_eq!(
1683 handle_route_watcher_event(
1684 &mut route_table,
1685 &mut fidl_route_map,
1686 &route_clients,
1687 add_event2,
1688 ),
1689 RouteEventOutcome::Noop
1690 );
1691 assert_eq!(
1692 fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1693 HashSet::from_iter([expected_route_message1.clone(), expected_route_message2.clone()])
1694 );
1695 assert_eq!(
1696 &right_sink.take_messages()[..],
1697 &[SentMessage::multicast(
1698 expected_route_message2
1699 .clone()
1700 .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
1701 right_group
1702 )]
1703 );
1704 assert_eq!(&wrong_sink.take_messages()[..], &[]);
1705
1706 assert_eq!(
1707 handle_route_watcher_event(
1708 &mut route_table,
1709 &mut fidl_route_map,
1710 &route_clients,
1711 remove_event,
1712 ),
1713 RouteEventOutcome::Noop
1714 );
1715 assert_eq!(
1716 fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1717 HashSet::from_iter([expected_route_message2.clone()])
1718 );
1719 assert_eq!(
1720 &right_sink.take_messages()[..],
1721 &[SentMessage::multicast(
1722 expected_route_message1.clone().into_rtnl_del_route(),
1723 right_group
1724 )]
1725 );
1726 assert_eq!(&wrong_sink.take_messages()[..], &[]);
1727
1728 assert_eq!(
1729 fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1730 HashSet::from_iter([expected_route_message2.clone()])
1731 );
1732 assert_eq!(&right_sink.take_messages()[..], &[]);
1733 assert_eq!(&wrong_sink.take_messages()[..], &[]);
1734 drop(route_clients);
1735 scope.join().await;
1736 }
1737
1738 #[ip_test(I, test = false)]
1740 #[fuchsia::test]
1741 async fn handles_route_watcher_event_unmanaged_route_table<
1742 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1743 >() {
1744 let _scope = fasync::Scope::new();
1745 let (subnet, next_hop) =
1746 I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1747 let installed_route: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1748 subnet,
1749 Some(next_hop),
1750 DEV1.into(),
1751 METRIC1,
1752 OTHER_FIDL_TABLE_ID,
1753 );
1754 let add_event = fnet_routes_ext::Event::Added(installed_route);
1755 let remove_event = fnet_routes_ext::Event::Removed(installed_route);
1756
1757 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1758 let (route_table_proxy, _route_table_server_end) =
1759 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1760 let (unmanaged_route_set_proxy, _server_end) =
1761 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1762 let (route_table_provider, _server_end) =
1763 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1764
1765 let mut route_table = RouteTableMap::new(
1766 route_table_proxy.clone(),
1767 MAIN_FIDL_TABLE_ID,
1768 unmanaged_route_set_proxy,
1769 route_table_provider,
1770 );
1771 let mut fidl_route_map = FidlRouteMap::<I>::default();
1772
1773 assert_eq!(
1775 handle_route_watcher_event(
1776 &mut route_table,
1777 &mut fidl_route_map,
1778 &route_clients,
1779 add_event,
1780 ),
1781 RouteEventOutcome::UpdateStash(AddOrRemoveRoute::Added(installed_route))
1782 );
1783
1784 assert_eq!(
1786 handle_route_watcher_event(
1787 &mut route_table,
1788 &mut fidl_route_map,
1789 &route_clients,
1790 remove_event,
1791 ),
1792 RouteEventOutcome::UpdateStash(AddOrRemoveRoute::Removed(installed_route))
1793 );
1794 }
1795
1796 #[ip_test(I, test = false)]
1797 #[fuchsia::test]
1798 #[should_panic(expected = "Netstack reported an unexpected route event")]
1799 async fn handles_unknown_route_watcher_event<
1800 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1801 >() {
1802 let (route_table_proxy, _route_table_server_end) =
1803 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1804 let (unmanaged_route_set_proxy, _server_end) =
1805 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1806 let (route_table_provider, _server_end) =
1807 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1808 let mut route_table = RouteTableMap::new(
1809 route_table_proxy.clone(),
1810 MAIN_FIDL_TABLE_ID,
1811 unmanaged_route_set_proxy,
1812 route_table_provider,
1813 );
1814 let mut fidl_route_map = FidlRouteMap::<I>::default();
1815 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1816
1817 let _ = handle_route_watcher_event(
1819 &mut route_table,
1820 &mut fidl_route_map,
1821 &route_clients,
1822 fnet_routes_ext::Event::Unknown,
1823 );
1824 }
1825
1826 #[ip_test(I, test = false)]
1827 #[fuchsia::test]
1828 #[should_panic(expected = "Netstack reported the addition of an existing route")]
1829 async fn handles_duplicate_route_watcher_event<
1830 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1831 >() {
1832 let (subnet, next_hop) =
1833 I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1834 let table_id = MAIN_FIDL_TABLE_ID;
1835 let installed_route: fnet_routes_ext::InstalledRoute<I> =
1836 create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
1837
1838 let (route_table_proxy, _route_table_server_end) =
1839 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1840 let (unmanaged_route_set_proxy, _server_end) =
1841 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1842 let (route_table_provider, _server_end) =
1843 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1844 let mut route_table = RouteTableMap::new(
1845 route_table_proxy.clone(),
1846 MAIN_FIDL_TABLE_ID,
1847 unmanaged_route_set_proxy,
1848 route_table_provider,
1849 );
1850 let mut fidl_route_map = FidlRouteMap::<I>::default();
1851 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1852
1853 for _ in 0..2 {
1855 assert_eq!(
1856 handle_route_watcher_event(
1857 &mut route_table,
1858 &mut fidl_route_map,
1859 &route_clients,
1860 fnet_routes_ext::Event::Added(installed_route),
1861 ),
1862 RouteEventOutcome::Noop
1863 );
1864 }
1865 }
1866
1867 #[ip_test(I, test = false)]
1868 #[fuchsia::test]
1869 #[should_panic(expected = "Netstack reported the removal of an unknown route")]
1870 async fn handles_remove_nonexisting_route_watcher_event<
1871 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1872 >() {
1873 let (subnet, next_hop) =
1874 I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1875 let table_id = MAIN_FIDL_TABLE_ID;
1876 let installed_route: fnet_routes_ext::InstalledRoute<I> =
1877 create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
1878
1879 let (route_table_proxy, _route_table_server_end) =
1880 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1881 let (unmanaged_route_set_proxy, _server_end) =
1882 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1883 let (route_table_provider, _server_end) =
1884 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1885 let mut route_table = RouteTableMap::new(
1886 route_table_proxy.clone(),
1887 MAIN_FIDL_TABLE_ID,
1888 unmanaged_route_set_proxy,
1889 route_table_provider,
1890 );
1891 let mut fidl_route_map = FidlRouteMap::<I>::default();
1892 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1893
1894 let _ = handle_route_watcher_event(
1896 &mut route_table,
1897 &mut fidl_route_map,
1898 &route_clients,
1899 fnet_routes_ext::Event::Removed(installed_route),
1900 );
1901 }
1902
1903 #[ip_test(I, test = false)]
1904 #[fuchsia::test]
1905 async fn handle_route_watcher_event_two_routesets<
1906 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1907 >() {
1908 let scope = fasync::Scope::new();
1909 let (subnet, next_hop) =
1910 I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1911
1912 let installed_route1: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1913 subnet,
1914 Some(next_hop),
1915 DEV1.into(),
1916 METRIC1,
1917 OTHER_FIDL_TABLE_ID,
1918 );
1919 let installed_route2: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1920 subnet,
1921 Some(next_hop),
1922 DEV2.into(),
1923 METRIC2,
1924 MAIN_FIDL_TABLE_ID,
1925 );
1926
1927 let add_events1 = [
1928 fnet_routes_ext::Event::Added(fnet_routes_ext::InstalledRoute {
1929 table_id: MAIN_FIDL_TABLE_ID,
1930 ..installed_route1
1931 }),
1932 fnet_routes_ext::Event::Added(installed_route1),
1933 ];
1934 let add_event2 = fnet_routes_ext::Event::Added(installed_route2);
1935 let remove_event = fnet_routes_ext::Event::Removed(installed_route1);
1936
1937 let expected_route_message1_unmanaged =
1940 NetlinkRouteMessage::try_from_installed_route(installed_route1, MAIN_ROUTE_TABLE_INDEX)
1941 .unwrap();
1942 let expected_route_message1_managed = NetlinkRouteMessage::try_from_installed_route(
1943 installed_route1,
1944 MANAGED_ROUTE_TABLE_INDEX,
1945 )
1946 .unwrap();
1947 let expected_route_message2: NetlinkRouteMessage =
1948 NetlinkRouteMessage::try_from_installed_route(installed_route2, MAIN_ROUTE_TABLE_INDEX)
1949 .unwrap();
1950
1951 let (right_group, wrong_group) = match I::VERSION {
1953 IpVersion::V4 => (
1954 ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1955 ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1956 ),
1957 IpVersion::V6 => (
1958 ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1959 ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1960 ),
1961 };
1962 let (mut right_sink, right_client, async_work_drain_task) =
1963 crate::client::testutil::new_fake_client::<NetlinkRoute>(
1964 crate::client::testutil::CLIENT_ID_1,
1965 [right_group],
1966 );
1967 let _join_handle = scope.spawn(async_work_drain_task);
1968 let (mut wrong_sink, wrong_client, async_work_drain_task) =
1969 crate::client::testutil::new_fake_client::<NetlinkRoute>(
1970 crate::client::testutil::CLIENT_ID_2,
1971 [wrong_group],
1972 );
1973 let _join_handle = scope.spawn(async_work_drain_task);
1974 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1975 route_clients.add_client(right_client);
1976 route_clients.add_client(wrong_client);
1977
1978 let (main_route_table_proxy, _route_table_server_end) =
1979 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1980 let (unmanaged_route_set_proxy, _unmanaged_route_set_server_end) =
1981 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1982 let (route_table_proxy, _route_table_server_end) =
1983 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1984 let (route_set_proxy, _server_end) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1985 let (route_table_provider, _server_end) =
1986 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1987
1988 let mut route_table = RouteTableMap::new(
1989 main_route_table_proxy,
1990 MAIN_FIDL_TABLE_ID,
1991 unmanaged_route_set_proxy,
1992 route_table_provider,
1993 );
1994 route_table.insert(
1995 MANAGED_ROUTE_TABLE_INDEX,
1996 RouteTable::Managed(ManagedRouteTable {
1997 route_set_proxy,
1998 route_table_proxy,
1999 fidl_table_id: OTHER_FIDL_TABLE_ID,
2000 rule_set_authenticated: false,
2001 }),
2002 );
2003
2004 let mut fidl_route_map = FidlRouteMap::<I>::default();
2005
2006 assert_eq!(
2009 handle_route_watcher_event(
2010 &mut route_table,
2011 &mut fidl_route_map,
2012 &route_clients,
2013 add_events1[0],
2014 ),
2015 RouteEventOutcome::Noop
2016 );
2017
2018 assert_eq!(
2020 &fidl_route_map
2021 .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2022 .collect::<HashSet<_>>(),
2023 &HashSet::new()
2024 );
2025
2026 assert_eq!(
2029 handle_route_watcher_event(
2030 &mut route_table,
2031 &mut fidl_route_map,
2032 &route_clients,
2033 add_events1[1],
2034 ),
2035 RouteEventOutcome::Noop
2036 );
2037
2038 assert_eq!(
2040 &fidl_route_map
2041 .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2042 .chain(fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX))
2043 .collect::<HashSet<_>>(),
2044 &HashSet::from_iter([
2045 expected_route_message1_unmanaged.clone(),
2046 expected_route_message1_managed.clone()
2047 ])
2048 );
2049 assert_eq!(
2050 &right_sink.take_messages()[..],
2051 &[expected_route_message1_unmanaged.clone(), expected_route_message1_managed.clone()]
2052 .map(|message| SentMessage::multicast(
2053 message.clone().into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
2054 right_group
2055 ))
2056 );
2057 assert_eq!(&wrong_sink.take_messages()[..], &[]);
2058
2059 assert_eq!(
2062 handle_route_watcher_event(
2063 &mut route_table,
2064 &mut fidl_route_map,
2065 &route_clients,
2066 add_event2,
2067 ),
2068 RouteEventOutcome::Noop
2069 );
2070
2071 assert_eq!(
2073 &fidl_route_map
2074 .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2075 .chain(fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX))
2076 .collect::<HashSet<_>>(),
2077 &HashSet::from_iter([
2078 expected_route_message1_unmanaged.clone(),
2079 expected_route_message1_managed.clone(),
2080 expected_route_message2.clone()
2081 ])
2082 );
2083
2084 assert_eq!(
2086 &right_sink.take_messages()[..],
2087 &[SentMessage::multicast(
2088 expected_route_message2
2089 .clone()
2090 .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
2091 right_group
2092 )]
2093 );
2094 assert_eq!(&wrong_sink.take_messages()[..], &[]);
2095
2096 assert_eq!(
2098 handle_route_watcher_event(
2099 &mut route_table,
2100 &mut fidl_route_map,
2101 &route_clients,
2102 remove_event,
2103 ),
2104 RouteEventOutcome::Cleanup(TableNeedsCleanup(
2105 OTHER_FIDL_TABLE_ID,
2106 MANAGED_ROUTE_TABLE_INDEX
2107 ))
2108 );
2109 assert_eq!(
2110 &fidl_route_map
2111 .iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX)
2112 .collect::<HashSet<_>>(),
2113 &HashSet::from_iter([
2114 expected_route_message1_unmanaged.clone(),
2115 expected_route_message2.clone()
2116 ])
2117 );
2118 assert_eq!(
2119 fidl_route_map
2120 .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2121 .collect::<HashSet<_>>(),
2122 HashSet::new()
2123 );
2124 assert_eq!(
2125 &right_sink.take_messages()[..],
2126 &[SentMessage::multicast(
2127 expected_route_message1_managed.clone().into_rtnl_del_route(),
2128 right_group
2129 )]
2130 );
2131 assert_eq!(&wrong_sink.take_messages()[..], &[]);
2132 drop(route_clients);
2133 scope.join().await;
2134 }
2135
2136 #[test_case(V4_SUB1, V4_NEXTHOP1)]
2137 #[test_case(V6_SUB1, V6_NEXTHOP1)]
2138 #[test_case(net_subnet_v4!("0.0.0.0/0"), net_ip_v4!("0.0.0.1"))]
2139 #[test_case(net_subnet_v6!("::/0"), net_ip_v6!("::1"))]
2140 fn test_netlink_route_message_try_from_installed_route<A: IpAddress>(
2141 subnet: Subnet<A>,
2142 next_hop: A,
2143 ) {
2144 netlink_route_message_conversion_helper::<A::Version>(subnet, next_hop);
2145 }
2146
2147 fn netlink_route_message_conversion_helper<I: Ip>(subnet: Subnet<I::Addr>, next_hop: I::Addr) {
2148 let installed_route = create_installed_route::<I>(
2149 subnet,
2150 Some(next_hop),
2151 DEV1.into(),
2152 METRIC1,
2153 MAIN_FIDL_TABLE_ID,
2154 );
2155 let prefix_length = subnet.prefix();
2156 let subnet = if prefix_length > 0 { Some(subnet) } else { None };
2157 let nlas = create_nlas::<I>(subnet, Some(next_hop), DEV1, METRIC1, None);
2158 let expected =
2159 create_netlink_route_message::<I>(prefix_length, MAIN_ROUTE_TABLE_INDEX, nlas);
2160
2161 let actual =
2162 NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX)
2163 .unwrap();
2164 assert_eq!(actual, expected);
2165 }
2166
2167 #[test_case(V4_SUB1)]
2168 #[test_case(V6_SUB1)]
2169 fn test_non_forward_route_conversion<A: IpAddress>(subnet: Subnet<A>) {
2170 let installed_route = fnet_routes_ext::InstalledRoute::<A::Version> {
2171 route: fnet_routes_ext::Route {
2172 destination: subnet,
2173 action: fnet_routes_ext::RouteAction::Unknown,
2174 properties: fnet_routes_ext::RouteProperties {
2175 specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
2176 metric: fnet_routes::SpecifiedMetric::ExplicitMetric(METRIC1),
2177 },
2178 },
2179 },
2180 effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric: METRIC1 },
2181 table_id: MAIN_FIDL_TABLE_ID,
2183 };
2184
2185 let actual: Result<NetlinkRouteMessage, NetlinkRouteMessageConversionError> =
2186 NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX);
2187 assert_eq!(actual, Err(NetlinkRouteMessageConversionError::RouteActionNotForwarding));
2188 }
2189
2190 #[fuchsia::test]
2191 fn test_oversized_interface_id_route_conversion() {
2192 let invalid_interface_id = (u32::MAX as u64) + 1;
2193 let installed_route: fnet_routes_ext::InstalledRoute<Ipv4> = create_installed_route(
2194 V4_SUB1,
2195 Some(V4_NEXTHOP1),
2196 invalid_interface_id,
2197 Default::default(),
2198 MAIN_FIDL_TABLE_ID,
2199 );
2200
2201 let actual: Result<NetlinkRouteMessage, NetlinkRouteMessageConversionError> =
2202 NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX);
2203 assert_eq!(
2204 actual,
2205 Err(NetlinkRouteMessageConversionError::InvalidInterfaceId(invalid_interface_id))
2206 );
2207 }
2208
2209 #[test]
2210 fn test_into_rtnl_new_route_is_serializable() {
2211 let route = create_netlink_route_message::<Ipv4>(0, MAIN_ROUTE_TABLE_INDEX, vec![]);
2212 let new_route_message = route.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false);
2213 let mut buf = vec![0; new_route_message.buffer_len()];
2214 new_route_message.serialize(&mut buf);
2216 }
2217
2218 #[test]
2219 fn test_into_rtnl_del_route_is_serializable() {
2220 let route = create_netlink_route_message::<Ipv6>(0, MAIN_ROUTE_TABLE_INDEX, vec![]);
2221 let del_route_message = route.into_rtnl_del_route();
2222 let mut buf = vec![0; del_route_message.buffer_len()];
2223 del_route_message.serialize(&mut buf);
2225 }
2226
2227 enum OnlyRoutes {}
2228 impl crate::route_eventloop::EventLoopSpec for OnlyRoutes {
2229 type InterfacesProxy = Required;
2230 type InterfacesHandler = Required;
2231 type RouteClients = Required;
2232
2233 type V4RoutesState = Optional;
2236 type V6RoutesState = Optional;
2237 type V4RoutesSetProvider = Optional;
2238 type V6RoutesSetProvider = Optional;
2239 type V4RouteTableProvider = Optional;
2240 type V6RouteTableProvider = Optional;
2241 type InterfacesStateProxy = Optional;
2242
2243 type InterfacesWorker = Optional;
2244 type RoutesV4Worker = Optional;
2245 type RoutesV6Worker = Optional;
2246 type RuleV4Worker = Optional;
2247 type RuleV6Worker = Optional;
2248 type NduseroptWorker = Optional;
2249 type NeighborWorker = Optional;
2250 }
2251
2252 struct Setup<W, R> {
2253 pub event_loop_inputs: crate::route_eventloop::EventLoopInputs<
2254 FakeInterfacesHandler,
2255 FakeSender<RouteNetlinkMessage>,
2256 OnlyRoutes,
2257 >,
2258 pub watcher_stream: W,
2259 pub route_sets: R,
2260 pub interfaces_request_stream: fnet_root::InterfacesRequestStream,
2261 pub request_sink:
2262 mpsc::Sender<crate::route_eventloop::UnifiedRequest<FakeSender<RouteNetlinkMessage>>>,
2263 pub async_work_sink: mpsc::UnboundedSender<AsyncWorkItem<NetlinkRoute>>,
2264 }
2265
2266 fn setup_with_route_clients_yielding_admin_server_ends<
2267 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2268 >(
2269 route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2270 ) -> Setup<
2271 impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2272 (ServerEnd<I::RouteTableMarker>, ServerEnd<I::RouteTableProviderMarker>),
2273 > {
2274 let (interfaces_handler, _interfaces_handler_sink) = FakeInterfacesHandler::new();
2275 let (request_sink, request_stream) = mpsc::channel(1);
2276 let (interfaces_proxy, interfaces) =
2277 fidl::endpoints::create_proxy::<fnet_root::InterfacesMarker>();
2278 let (async_work_sink, async_work_receiver) = mpsc::unbounded();
2279
2280 #[derive(GenericOverIp)]
2281 #[generic_over_ip(I, Ip)]
2282 struct ServerEnds<
2283 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2284 > {
2285 routes_state: ServerEnd<I::StateMarker>,
2286 routes_set_provider: ServerEnd<I::RouteTableMarker>,
2287 route_table_provider: ServerEnd<I::RouteTableProviderMarker>,
2288 }
2289
2290 let base_inputs = crate::route_eventloop::EventLoopInputs {
2291 interfaces_handler: EventLoopComponent::Present(interfaces_handler),
2292 route_clients: EventLoopComponent::Present(route_clients),
2293 interfaces_proxy: EventLoopComponent::Present(interfaces_proxy),
2294 async_work_receiver,
2295
2296 interfaces_state_proxy: EventLoopComponent::Absent(Optional),
2297 v4_routes_state: EventLoopComponent::Absent(Optional),
2298 v6_routes_state: EventLoopComponent::Absent(Optional),
2299 v4_main_route_table: EventLoopComponent::Absent(Optional),
2300 v6_main_route_table: EventLoopComponent::Absent(Optional),
2301 v4_route_table_provider: EventLoopComponent::Absent(Optional),
2302 v6_route_table_provider: EventLoopComponent::Absent(Optional),
2303 v4_rule_table: EventLoopComponent::Absent(Optional),
2304 v6_rule_table: EventLoopComponent::Absent(Optional),
2305 ndp_option_watcher_provider: EventLoopComponent::Absent(Optional),
2306 neighbors_view: EventLoopComponent::Absent(Optional),
2307 neighbors_controller: EventLoopComponent::Absent(Optional),
2308
2309 unified_request_stream: request_stream,
2310 };
2311
2312 let (IpInvariant(inputs), server_ends) = I::map_ip_out(
2313 base_inputs,
2314 |base_inputs| {
2315 let (v4_routes_state, routes_state) =
2316 fidl::endpoints::create_proxy::<fnet_routes::StateV4Marker>();
2317 let (v4_main_route_table, routes_set_provider) =
2318 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV4Marker>();
2319 let (v4_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2320 fnet_routes_admin::RouteTableProviderV4Marker,
2321 >();
2322 let inputs = crate::route_eventloop::EventLoopInputs {
2323 v4_routes_state: EventLoopComponent::Present(v4_routes_state),
2324 v4_main_route_table: EventLoopComponent::Present(v4_main_route_table),
2325 v4_route_table_provider: EventLoopComponent::Present(v4_route_table_provider),
2326 ..base_inputs
2327 };
2328 let server_ends =
2329 ServerEnds::<Ipv4> { routes_state, routes_set_provider, route_table_provider };
2330 (IpInvariant(inputs), server_ends)
2331 },
2332 |base_inputs| {
2333 let (v6_routes_state, routes_state) =
2334 fidl::endpoints::create_proxy::<fnet_routes::StateV6Marker>();
2335 let (v6_main_route_table, routes_set_provider) =
2336 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV6Marker>();
2337 let (v6_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2338 fnet_routes_admin::RouteTableProviderV6Marker,
2339 >();
2340 let inputs = crate::route_eventloop::EventLoopInputs {
2341 v6_routes_state: EventLoopComponent::Present(v6_routes_state),
2342 v6_main_route_table: EventLoopComponent::Present(v6_main_route_table),
2343 v6_route_table_provider: EventLoopComponent::Present(v6_route_table_provider),
2344 ..base_inputs
2345 };
2346 let server_ends =
2347 ServerEnds::<Ipv6> { routes_state, routes_set_provider, route_table_provider };
2348 (IpInvariant(inputs), server_ends)
2349 },
2350 );
2351
2352 let ServerEnds { routes_state, routes_set_provider, route_table_provider } = server_ends;
2353
2354 let state_stream = routes_state.into_stream().boxed_local();
2355
2356 let interfaces_request_stream = interfaces.into_stream();
2357
2358 #[derive(GenericOverIp)]
2359 #[generic_over_ip(I, Ip)]
2360 struct StateRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2361 request: <<I::StateMarker as ProtocolMarker>::RequestStream as futures::Stream>::Item,
2362 }
2363
2364 #[derive(GenericOverIp)]
2365 #[generic_over_ip(I, Ip)]
2366 struct WatcherRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2367 watcher: <I::WatcherMarker as ProtocolMarker>::RequestStream,
2368 }
2369
2370 let watcher_stream = state_stream
2371 .map(|request| {
2372 let wrapper = I::map_ip(
2373 StateRequestWrapper { request },
2374 |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2375 fnet_routes::StateV4Request::GetWatcherV4 {
2376 options: _,
2377 watcher,
2378 control_handle: _,
2379 } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2380 fnet_routes::StateV4Request::GetRuleWatcherV4 {
2381 options: _,
2382 watcher: _,
2383 control_handle: _,
2384 } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2385 },
2386 |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2387 fnet_routes::StateV6Request::GetWatcherV6 {
2388 options: _,
2389 watcher,
2390 control_handle: _,
2391 } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2392 fnet_routes::StateV6Request::GetRuleWatcherV6 {
2393 options: _,
2394 watcher: _,
2395 control_handle: _,
2396 } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2397 },
2398 );
2399 wrapper
2400 })
2401 .map(|WatcherRequestWrapper { watcher }| watcher)
2402 .flatten()
2405 .fuse();
2406
2407 Setup {
2408 event_loop_inputs: inputs,
2409 watcher_stream,
2410 route_sets: (routes_set_provider, route_table_provider),
2411 interfaces_request_stream,
2412 request_sink,
2413 async_work_sink,
2414 }
2415 }
2416
2417 fn setup_with_route_clients<
2418 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2419 >(
2420 route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2421 ) -> Setup<
2422 impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2423 impl Stream<
2424 Item = (
2425 fnet_routes_ext::TableId,
2426 <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2427 ),
2428 >,
2429 > {
2430 let Setup {
2431 event_loop_inputs,
2432 watcher_stream,
2433 route_sets: (routes_set_provider, route_table_provider),
2434 interfaces_request_stream,
2435 request_sink,
2436 async_work_sink,
2437 } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
2438 let route_set_stream =
2439 fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2440 routes_set_provider,
2441 Some(MAIN_FIDL_TABLE_ID),
2442 )
2443 .map(|item| (MAIN_FIDL_TABLE_ID, item));
2444
2445 let route_table_provider_request_stream = route_table_provider.into_stream();
2446
2447 let table_id = AtomicU32::new(OTHER_FIDL_TABLE_ID.get());
2448
2449 let route_sets_from_route_table_provider =
2450 futures::TryStreamExt::map_ok(route_table_provider_request_stream, move |request| {
2451 match I::into_route_table_provider_request(request) {
2452 fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
2453 provider,
2454 options: _,
2455 control_handle: _,
2456 } => {
2457 let table_id =
2458 fnet_routes_ext::TableId::new(table_id.fetch_add(1, Ordering::SeqCst));
2459 fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2460 provider,
2461 Some(table_id),
2462 )
2463 .map(move |route_set_request| (table_id, route_set_request))
2464 }
2465 r => panic!("unexpected request {r:?}"),
2466 }
2467 })
2468 .map(|result| result.expect("should not get FIDL error"))
2469 .flatten_unordered(None)
2470 .fuse();
2471 let route_set_stream = futures::stream::select_all([
2472 route_set_stream.left_stream(),
2473 route_sets_from_route_table_provider.right_stream(),
2474 ])
2475 .fuse();
2476
2477 Setup {
2478 event_loop_inputs,
2479 watcher_stream,
2480 route_sets: route_set_stream,
2481 interfaces_request_stream,
2482 request_sink,
2483 async_work_sink,
2484 }
2485 }
2486
2487 async fn respond_to_watcher<
2488 I: fnet_routes_ext::FidlRouteIpExt,
2489 S: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2490 >(
2491 stream: S,
2492 updates: impl IntoIterator<Item = I::WatchEvent>,
2493 ) {
2494 #[derive(GenericOverIp)]
2495 #[generic_over_ip(I, Ip)]
2496 struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
2497 request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2498 update: I::WatchEvent,
2499 }
2500 stream
2501 .zip(futures::stream::iter(updates.into_iter()))
2502 .for_each(|(request, update)| async move {
2503 I::map_ip_in(
2504 HandleInputs { request, update },
2505 |HandleInputs { request, update }| match request
2506 .expect("failed to receive `Watch` request")
2507 {
2508 fnet_routes::WatcherV4Request::Watch { responder } => {
2509 responder.send(&[update]).expect("failed to respond to `Watch`")
2510 }
2511 },
2512 |HandleInputs { request, update }| match request
2513 .expect("failed to receive `Watch` request")
2514 {
2515 fnet_routes::WatcherV6Request::Watch { responder } => {
2516 responder.send(&[update]).expect("failed to respond to `Watch`")
2517 }
2518 },
2519 );
2520 })
2521 .await;
2522 }
2523
2524 async fn run_event_loop<I: Ip>(
2525 inputs: crate::route_eventloop::EventLoopInputs<
2526 FakeInterfacesHandler,
2527 FakeSender<RouteNetlinkMessage>,
2528 OnlyRoutes,
2529 >,
2530 ) -> Never {
2531 let included_workers = match I::VERSION {
2532 IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
2533 routes_v4: EventLoopComponent::Present(()),
2534 routes_v6: EventLoopComponent::Absent(Optional),
2535 interfaces: EventLoopComponent::Absent(Optional),
2536 rules_v4: EventLoopComponent::Absent(Optional),
2537 rules_v6: EventLoopComponent::Absent(Optional),
2538 nduseropt: EventLoopComponent::Absent(Optional),
2539 neighbors: EventLoopComponent::Absent(Optional),
2540 },
2541 IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
2542 routes_v4: EventLoopComponent::Absent(Optional),
2543 routes_v6: EventLoopComponent::Present(()),
2544 interfaces: EventLoopComponent::Absent(Optional),
2545 rules_v4: EventLoopComponent::Absent(Optional),
2546 rules_v6: EventLoopComponent::Absent(Optional),
2547 nduseropt: EventLoopComponent::Absent(Optional),
2548 neighbors: EventLoopComponent::Absent(Optional),
2549 },
2550 };
2551
2552 let event_loop = inputs.initialize(included_workers).await;
2553 event_loop.run().await
2554 }
2555
2556 fn get_test_route_events_new_route_args<A: IpAddress>(
2557 subnet: Subnet<A>,
2558 next_hop1: A,
2559 next_hop2: A,
2560 ) -> [RequestArgs<A::Version>; 2]
2561 where
2562 A::Version: fnet_routes_ext::FidlRouteIpExt,
2563 {
2564 [
2565 RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2566 create_unicast_new_route_args(
2567 subnet,
2568 next_hop1,
2569 DEV1.into(),
2570 METRIC1,
2571 MANAGED_ROUTE_TABLE_INDEX,
2572 ),
2573 ))),
2574 RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2575 create_unicast_new_route_args(
2576 subnet,
2577 next_hop2,
2578 DEV2.into(),
2579 METRIC2,
2580 MANAGED_ROUTE_TABLE_INDEX,
2581 ),
2582 ))),
2583 ]
2584 }
2585
2586 fn create_unicast_new_route_args<A: IpAddress>(
2587 subnet: Subnet<A>,
2588 next_hop: A,
2589 interface_id: u64,
2590 priority: u32,
2591 table: NetlinkRouteTableIndex,
2592 ) -> UnicastNewRouteArgs<A::Version> {
2593 UnicastNewRouteArgs {
2594 subnet,
2595 target: fnet_routes_ext::RouteTarget {
2596 outbound_interface: interface_id,
2597 next_hop: SpecifiedAddr::new(next_hop),
2598 },
2599 priority: NonZeroU32::new(priority),
2600 table,
2601 }
2602 }
2603
2604 fn create_unicast_del_route_args<A: IpAddress>(
2605 subnet: Subnet<A>,
2606 next_hop: Option<A>,
2607 interface_id: Option<u64>,
2608 priority: Option<u32>,
2609 table: NetlinkRouteTableIndex,
2610 ) -> UnicastDelRouteArgs<A::Version> {
2611 UnicastDelRouteArgs {
2612 subnet,
2613 outbound_interface: interface_id.map(NonZeroU64::new).flatten(),
2614 next_hop: next_hop.map(SpecifiedAddr::new).flatten(),
2615 priority: priority.map(NonZeroU32::new).flatten(),
2616 table: NonZeroNetlinkRouteTableIndex::new(table).unwrap(),
2617 }
2618 }
2619
2620 #[derive(Debug, PartialEq)]
2621 struct TestRequestResult {
2622 messages: Vec<SentMessage<RouteNetlinkMessage>>,
2623 waiter_results: Vec<Result<(), RequestError>>,
2624 }
2625
2626 async fn test_requests<
2632 A: IpAddress,
2633 Fut: Future<Output = ()>,
2634 F: FnOnce(fnet_root::InterfacesRequestStream) -> Fut,
2635 >(
2636 args: impl IntoIterator<Item = RequestArgs<A::Version>>,
2637 root_handler: F,
2638 route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2639 subnet: Subnet<A>,
2640 next_hop1: A,
2641 next_hop2: A,
2642 num_sink_messages: usize,
2643 ) -> TestRequestResult
2644 where
2645 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2646 {
2647 let scope = fasync::Scope::new();
2648 let result = {
2649 let (mut route_sink, route_client, async_work_drain_task) =
2650 crate::client::testutil::new_fake_client::<NetlinkRoute>(
2651 crate::client::testutil::CLIENT_ID_1,
2652 [ModernGroup(match A::Version::VERSION {
2653 IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
2654 IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
2655 })],
2656 );
2657 let _join_handle = scope.spawn(async_work_drain_task);
2658 let (mut other_sink, other_client, async_work_drain_task) =
2659 crate::client::testutil::new_fake_client::<NetlinkRoute>(
2660 crate::client::testutil::CLIENT_ID_2,
2661 [ModernGroup(rtnetlink_groups_RTNLGRP_LINK)],
2662 );
2663 let _join_handle = scope.spawn(async_work_drain_task);
2664 let Setup {
2665 event_loop_inputs,
2666 mut watcher_stream,
2667 route_sets: mut route_set_stream,
2668 interfaces_request_stream,
2669 request_sink,
2670 async_work_sink: _,
2671 } = setup_with_route_clients::<A::Version>({
2672 let route_clients = ClientTable::default();
2673 route_clients.add_client(route_client.clone());
2674 route_clients.add_client(other_client);
2675 route_clients
2676 });
2677
2678 let mut event_loop_fut = pin!(run_event_loop::<A::Version>(event_loop_inputs).fuse());
2679
2680 let watcher_stream_fut = respond_to_watcher::<A::Version, _>(
2681 watcher_stream.by_ref(),
2682 std::iter::once(fnet_routes_ext::Event::<A::Version>::Idle.try_into().unwrap()),
2683 );
2684 futures::select! {
2685 () = watcher_stream_fut.fuse() => {},
2686 err = event_loop_fut => unreachable!("eventloop should not return: {err:?}"),
2687 }
2688 assert_eq!(&route_sink.take_messages()[..], &[]);
2689 assert_eq!(&other_sink.take_messages()[..], &[]);
2690
2691 let route_client = &route_client;
2692 let fut = async {
2693 let initial_new_routes =
2695 get_test_route_events_new_route_args(subnet, next_hop1, next_hop2);
2696 let count_initial_new_routes = initial_new_routes.len();
2697
2698 let request_sink = futures::stream::iter(initial_new_routes)
2699 .fold(request_sink, |mut request_sink, args| async move {
2700 let (completer, waiter) = oneshot::channel();
2701 request_sink
2702 .send(
2703 Request {
2704 args,
2705 sequence_number: TEST_SEQUENCE_NUMBER,
2706 client: route_client.clone(),
2707 completer,
2708 }
2709 .into(),
2710 )
2711 .await
2712 .unwrap();
2713 assert_matches!(waiter.await.unwrap(), Ok(()));
2714 request_sink
2715 })
2716 .await;
2717
2718 for _ in 0..count_initial_new_routes {
2722 let _ = route_sink.next_message().await;
2723 }
2724 assert_eq!(route_sink.next_message().now_or_never(), None);
2725
2726 let (results, _request_sink) = futures::stream::iter(args)
2727 .fold(
2728 (Vec::new(), request_sink),
2729 |(mut results, mut request_sink), args| async move {
2730 let (completer, waiter) = oneshot::channel();
2731 request_sink
2732 .send(
2733 Request {
2734 args,
2735 sequence_number: TEST_SEQUENCE_NUMBER,
2736 client: route_client.clone(),
2737 completer,
2738 }
2739 .into(),
2740 )
2741 .await
2742 .unwrap();
2743 results.push(waiter.await.unwrap());
2744 (results, request_sink)
2745 },
2746 )
2747 .await;
2748
2749 let messages = {
2750 assert_eq!(&other_sink.take_messages()[..], &[]);
2751 let mut messages = Vec::new();
2752 while messages.len() < num_sink_messages {
2753 messages.push(route_sink.next_message().await);
2754 }
2755 assert_eq!(route_sink.next_message().now_or_never(), None);
2756 messages
2757 };
2758
2759 (messages, results)
2760 };
2761
2762 let route_set_fut = respond_to_route_set_modifications::<A::Version, _, _>(
2763 route_set_stream.by_ref(),
2764 watcher_stream.by_ref(),
2765 route_set_results,
2766 )
2767 .fuse();
2768
2769 let root_interfaces_fut = root_handler(interfaces_request_stream).fuse();
2770
2771 let (messages, results) = futures::select! {
2772 (messages, results) = fut.fuse() => (messages, results),
2773 res = futures::future::join3(
2774 route_set_fut,
2775 root_interfaces_fut,
2776 event_loop_fut,
2777 ) => {
2778 unreachable!("eventloop/stream handlers should not return: {res:?}")
2779 }
2780 };
2781
2782 TestRequestResult { messages, waiter_results: results }
2783 };
2784 scope.join().await;
2785 result
2786 }
2787
2788 #[test_case(V4_SUB1, V4_NEXTHOP1, V4_NEXTHOP2; "v4_route_dump")]
2789 #[test_case(V6_SUB1, V6_NEXTHOP1, V6_NEXTHOP2; "v6_route_dump")]
2790 #[fuchsia::test]
2791 async fn test_get_route<A: IpAddress>(subnet: Subnet<A>, next_hop1: A, next_hop2: A)
2792 where
2793 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2794 {
2795 let expected_messages = vec![
2796 SentMessage::unicast(
2797 create_netlink_route_message::<A::Version>(
2798 subnet.prefix(),
2799 MANAGED_ROUTE_TABLE_INDEX,
2800 create_nlas::<A::Version>(
2801 Some(subnet),
2802 Some(next_hop1),
2803 DEV1,
2804 METRIC1,
2805 Some(MANAGED_ROUTE_TABLE_ID),
2806 ),
2807 )
2808 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2809 ),
2810 SentMessage::unicast(
2811 create_netlink_route_message::<A::Version>(
2812 subnet.prefix(),
2813 MANAGED_ROUTE_TABLE_INDEX,
2814 create_nlas::<A::Version>(
2815 Some(subnet),
2816 Some(next_hop2),
2817 DEV2,
2818 METRIC2,
2819 Some(MANAGED_ROUTE_TABLE_ID),
2820 ),
2821 )
2822 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2823 ),
2824 ];
2825
2826 pretty_assertions::assert_eq!(
2827 {
2828 let mut test_request_result = test_requests(
2829 [RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump))],
2830 |interfaces_request_stream| async {
2831 interfaces_request_stream
2832 .for_each(|req| async move {
2833 panic!("unexpected InterfacesRequest: {req:?}")
2834 })
2835 .await;
2836 },
2837 HashMap::new(),
2838 subnet,
2839 next_hop1,
2840 next_hop2,
2841 expected_messages.len(),
2842 )
2843 .await;
2844 test_request_result.messages.sort_by_key(|message| {
2845 assert_matches!(
2846 &message.message.payload,
2847 NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m)) => {
2848 m.attributes.clone().into_iter().filter_map(|nla|
2851 match nla {
2852 RouteAttribute::Oif(interface_id) =>
2853 Some((m.header.address_family, interface_id)),
2854 RouteAttribute::Destination(_)
2855 | RouteAttribute::Gateway(_)
2856 | RouteAttribute::Priority(_)
2857 | RouteAttribute::Table(_) => None,
2858 _ => panic!("unexpected NLA {nla:?} present in payload"),
2859 }
2860 ).next()
2861 }
2862 )
2863 });
2864 test_request_result
2865 },
2866 TestRequestResult { messages: expected_messages, waiter_results: vec![Ok(())] },
2867 )
2868 }
2869
2870 #[derive(Debug, Clone, Copy)]
2871 enum RouteSetResult {
2872 AddResult(Result<bool, fnet_routes_admin::RouteSetError>),
2873 DelResult(Result<bool, fnet_routes_admin::RouteSetError>),
2874 AuthenticationResult(Result<(), fnet_routes_admin::AuthenticateForInterfaceError>),
2875 }
2876
2877 fn route_event_from_route<
2878 I: Ip + fnet_routes_ext::FidlRouteIpExt,
2879 F: FnOnce(fnet_routes_ext::InstalledRoute<I>) -> fnet_routes_ext::Event<I>,
2880 >(
2881 route: I::Route,
2882 table_id: fnet_routes_ext::TableId,
2883 event_fn: F,
2884 ) -> I::WatchEvent {
2885 let route: fnet_routes_ext::Route<I> = route.try_into().unwrap();
2886
2887 let metric = match route.properties.specified_properties.metric {
2888 fnet_routes::SpecifiedMetric::ExplicitMetric(metric) => metric,
2889 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) => {
2890 panic!("metric should be explicit")
2891 }
2892 };
2893
2894 event_fn(fnet_routes_ext::InstalledRoute {
2895 route,
2896 effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
2897 table_id,
2899 })
2900 .try_into()
2901 .unwrap()
2902 }
2903
2904 async fn respond_to_route_set_modifications<
2907 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2908 RS: Stream<
2909 Item = (
2910 fnet_routes_ext::TableId,
2911 <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2912 ),
2913 >,
2914 WS: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>
2915 + std::marker::Unpin,
2916 >(
2917 route_stream: RS,
2918 watcher_stream: WS,
2919 mut route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2920 ) {
2921 #[derive(GenericOverIp)]
2922 #[generic_over_ip(I, Ip)]
2923 struct RouteSetInputs<I: fnet_routes_ext::admin::FidlRouteAdminIpExt> {
2924 request: <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2925 route_set_result: RouteSetResult,
2926 }
2927 #[derive(GenericOverIp)]
2928 #[generic_over_ip(I, Ip)]
2929 struct RouteSetOutputs<I: fnet_routes_ext::FidlRouteIpExt> {
2930 event: Option<I::WatchEvent>,
2931 }
2932
2933 let mut route_stream = std::pin::pin!(route_stream);
2934 let mut watcher_stream = std::pin::pin!(watcher_stream);
2935
2936 {
2937 let queue = route_set_results.entry(OTHER_FIDL_TABLE_ID).or_default();
2938 queue.push_front(RouteSetResult::AddResult(Ok(true)));
2939 queue.push_front(RouteSetResult::AddResult(Ok(true)));
2940 }
2941
2942 while let Some((table_id, request)) = route_stream.next().await {
2943 let route_set_result = route_set_results
2944 .get_mut(&table_id)
2945 .unwrap_or_else(|| panic!("missing result for {table_id:?}"))
2946 .pop_front()
2947 .unwrap_or_else(|| panic!("missing result for {table_id:?}"));
2948 let RouteSetOutputs { event } = I::map_ip(
2949 RouteSetInputs { request, route_set_result },
2950 |RouteSetInputs { request, route_set_result }| {
2951 let request = request.expect("failed to receive request");
2952 crate::logging::log_debug!(
2953 "responding on {table_id:?} to route set request {request:?} \
2954 with result {route_set_result:?}"
2955 );
2956 match request {
2957 fnet_routes_admin::RouteSetV4Request::AddRoute { route, responder } => {
2958 let route_set_result = assert_matches!(
2959 route_set_result,
2960 RouteSetResult::AddResult(res) => res
2961 );
2962
2963 responder
2964 .send(route_set_result)
2965 .expect("failed to respond to `AddRoute`");
2966
2967 RouteSetOutputs {
2968 event: match route_set_result {
2969 Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2970 route,
2971 table_id,
2972 fnet_routes_ext::Event::<Ipv4>::Added,
2973 )),
2974 _ => None,
2975 },
2976 }
2977 }
2978 fnet_routes_admin::RouteSetV4Request::RemoveRoute { route, responder } => {
2979 let route_set_result = assert_matches!(
2980 route_set_result,
2981 RouteSetResult::DelResult(res) => res
2982 );
2983
2984 responder
2985 .send(route_set_result)
2986 .expect("failed to respond to `RemoveRoute`");
2987
2988 RouteSetOutputs {
2989 event: match route_set_result {
2990 Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2991 route,
2992 table_id,
2993 fnet_routes_ext::Event::<Ipv4>::Removed,
2994 )),
2995 _ => None,
2996 },
2997 }
2998 }
2999 fnet_routes_admin::RouteSetV4Request::AuthenticateForInterface {
3000 credential: _,
3001 responder,
3002 } => {
3003 let route_set_result = assert_matches!(
3004 route_set_result,
3005 RouteSetResult::AuthenticationResult(res) => res
3006 );
3007
3008 responder
3009 .send(route_set_result)
3010 .expect("failed to respond to `AuthenticateForInterface`");
3011 RouteSetOutputs { event: None }
3012 }
3013 }
3014 },
3015 |RouteSetInputs { request, route_set_result }| {
3016 let request = request.expect("failed to receive request");
3017 crate::logging::log_debug!(
3018 "responding on {table_id:?} to route set request {request:?} \
3019 with result {route_set_result:?}"
3020 );
3021 match request {
3022 fnet_routes_admin::RouteSetV6Request::AddRoute { route, responder } => {
3023 let route_set_result = assert_matches!(
3024 route_set_result,
3025 RouteSetResult::AddResult(res) => res
3026 );
3027
3028 responder
3029 .send(route_set_result)
3030 .expect("failed to respond to `AddRoute`");
3031
3032 RouteSetOutputs {
3033 event: match route_set_result {
3034 Ok(true) => Some(route_event_from_route::<Ipv6, _>(
3035 route,
3036 table_id,
3037 fnet_routes_ext::Event::<Ipv6>::Added,
3038 )),
3039 _ => None,
3040 },
3041 }
3042 }
3043 fnet_routes_admin::RouteSetV6Request::RemoveRoute { route, responder } => {
3044 let route_set_result = assert_matches!(
3045 route_set_result,
3046 RouteSetResult::DelResult(res) => res
3047 );
3048
3049 responder
3050 .send(route_set_result)
3051 .expect("failed to respond to `RemoveRoute`");
3052
3053 RouteSetOutputs {
3054 event: match route_set_result {
3055 Ok(true) => Some(route_event_from_route::<Ipv6, _>(
3056 route,
3057 table_id,
3058 fnet_routes_ext::Event::<Ipv6>::Removed,
3059 )),
3060 _ => None,
3061 },
3062 }
3063 }
3064 fnet_routes_admin::RouteSetV6Request::AuthenticateForInterface {
3065 credential: _,
3066 responder,
3067 } => {
3068 let route_set_result = assert_matches!(
3069 route_set_result,
3070 RouteSetResult::AuthenticationResult(res) => res
3071 );
3072
3073 responder
3074 .send(route_set_result)
3075 .expect("failed to respond to `AuthenticateForInterface`");
3076 RouteSetOutputs { event: None }
3077 }
3078 }
3079 },
3080 );
3081
3082 if let Some(update) = event {
3083 let request = watcher_stream.next().await.expect("watcher stream should not end");
3084
3085 #[derive(GenericOverIp)]
3086 #[generic_over_ip(I, Ip)]
3087 struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
3088 request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
3089 update: I::WatchEvent,
3090 }
3091
3092 I::map_ip_in(
3093 HandleInputs { request, update },
3094 |HandleInputs { request, update }| match request
3095 .expect("failed to receive `Watch` request")
3096 {
3097 fnet_routes::WatcherV4Request::Watch { responder } => {
3098 responder.send(&[update]).expect("failed to respond to `Watch`")
3099 }
3100 },
3101 |HandleInputs { request, update }| match request
3102 .expect("failed to receive `Watch` request")
3103 {
3104 fnet_routes::WatcherV6Request::Watch { responder } => {
3105 responder.send(&[update]).expect("failed to respond to `Watch`")
3106 }
3107 },
3108 );
3109 }
3110 }
3111
3112 if route_set_results.values().any(|value| !value.is_empty()) {
3113 panic!("unused route_set_results entries: {route_set_results:?}");
3114 }
3115 }
3116
3117 async fn test_route_requests<
3122 A: IpAddress,
3123 Fut: Future<Output = ()>,
3124 F: FnMut(fnet_interfaces_admin::ControlRequest) -> Fut,
3125 >(
3126 args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3127 mut control_request_handler: F,
3128 route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3129 subnet: Subnet<A>,
3130 next_hop1: A,
3131 next_hop2: A,
3132 num_sink_messages: usize,
3133 ) -> TestRequestResult
3134 where
3135 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3136 {
3137 test_requests(
3138 args,
3139 |interfaces_request_stream| async move {
3140 interfaces_request_stream
3141 .filter_map(|req| {
3142 futures::future::ready(match req.unwrap() {
3143 fnet_root::InterfacesRequest::GetAdmin {
3144 id,
3145 control,
3146 control_handle: _,
3147 } => {
3148 pretty_assertions::assert_eq!(id, DEV1 as u64);
3149 Some(control.into_stream())
3150 }
3151 req => unreachable!("unexpected interfaces request: {req:?}"),
3152 })
3153 })
3154 .flatten()
3155 .next()
3156 .then(|req| control_request_handler(req.unwrap().unwrap()))
3157 .await
3158 },
3159 route_set_results,
3160 subnet,
3161 next_hop1,
3162 next_hop2,
3163 num_sink_messages,
3164 )
3165 .await
3166 }
3167
3168 async fn test_route_requests_helper<A: IpAddress>(
3171 args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3172 expected_messages: Vec<SentMessage<RouteNetlinkMessage>>,
3173 route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3174 waiter_results: Vec<Result<(), RequestError>>,
3175 subnet: Subnet<A>,
3176 ) where
3177 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3178 {
3179 let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3180 (),
3181 |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3182 |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3183 );
3184
3185 pretty_assertions::assert_eq!(
3186 {
3187 let mut test_request_result = test_route_requests(
3188 args,
3189 |req| async {
3190 match req {
3191 fnet_interfaces_admin::ControlRequest::GetAuthorizationForInterface {
3192 responder,
3193 } => {
3194 let token = fidl::Event::create();
3195 let grant = fnet_resources::GrantForInterfaceAuthorization {
3196 interface_id: DEV1 as u64,
3197 token,
3198 };
3199 responder.send(grant).unwrap();
3200 }
3201 req => panic!("unexpected request {req:?}"),
3202 }
3203 },
3204 route_set_results,
3205 subnet,
3206 next_hop1,
3207 next_hop2,
3208 expected_messages.len(),
3209 )
3210 .await;
3211 test_request_result.messages.sort_by_key(|message| {
3212 let sequence_number = message.message.header.sequence_number;
3215 assert_matches!(
3216 &message.message.payload,
3217 NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m))
3218 | NetlinkPayload::InnerMessage(RouteNetlinkMessage::DelRoute(m)) => {
3219 m.attributes.clone().into_iter().filter_map(|nla|
3222 match nla {
3223 RouteAttribute::Priority(priority) =>
3224 Some((sequence_number, priority)),
3225 RouteAttribute::Destination(_)
3226 | RouteAttribute::Gateway(_)
3227 | RouteAttribute::Oif(_)
3228 | RouteAttribute::Table(_) => None,
3229 _ => panic!("unexpected NLA {nla:?} present in payload"),
3230 }
3231 ).next()
3232 }
3233 )
3234 });
3235 test_request_result
3236 },
3237 TestRequestResult { messages: expected_messages, waiter_results },
3238 )
3239 }
3240
3241 enum RouteRequestKind {
3242 New,
3243 Del,
3244 }
3245
3246 fn route_set_for_table_id(
3247 results: Vec<RouteSetResult>,
3248 table_id: fnet_routes_ext::TableId,
3249 ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3250 HashMap::from_iter([(table_id, results.into())])
3251 }
3252
3253 fn route_set_for_first_new_table(
3254 results: Vec<RouteSetResult>,
3255 ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3256 route_set_for_table_id(results, OTHER_FIDL_TABLE_ID)
3257 }
3258
3259 #[test_case(
3261 RouteRequestKind::New,
3262 vec![
3263 RouteSetResult::AddResult(Ok(true))
3264 ],
3265 Ok(()),
3266 V4_SUB1,
3267 Some(METRIC3),
3268 DEV1;
3269 "v4_new_success")]
3270 #[test_case(
3271 RouteRequestKind::New,
3272 vec![
3273 RouteSetResult::AddResult(Ok(true))
3274 ],
3275 Ok(()),
3276 V6_SUB1,
3277 Some(METRIC3),
3278 DEV1;
3279 "v6_new_success")]
3280 #[test_case(
3281 RouteRequestKind::New,
3282 vec![
3283 RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3284 RouteSetResult::AuthenticationResult(Err(
3285 fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3286 )),
3287 ],
3288 Err(RequestError::UnrecognizedInterface),
3289 V4_SUB1,
3290 Some(METRIC3),
3291 DEV1;
3292 "v4_new_failed_auth")]
3293 #[test_case(
3294 RouteRequestKind::New,
3295 vec![
3296 RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3297 RouteSetResult::AuthenticationResult(Err(
3298 fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3299 )),
3300 ],
3301 Err(RequestError::UnrecognizedInterface),
3302 V6_SUB1,
3303 Some(METRIC3),
3304 DEV1;
3305 "v6_new_failed_auth")]
3306 #[test_case(
3307 RouteRequestKind::New,
3308 vec![
3309 RouteSetResult::AddResult(Ok(false))
3310 ],
3311 Err(RequestError::AlreadyExists),
3312 V4_SUB1,
3313 Some(METRIC3),
3314 DEV1;
3315 "v4_new_failed_netstack_reports_exists")]
3316 #[test_case(
3317 RouteRequestKind::New,
3318 vec![
3319 RouteSetResult::AddResult(Ok(false))
3320 ],
3321 Err(RequestError::AlreadyExists),
3322 V6_SUB1,
3323 Some(METRIC3),
3324 DEV1;
3325 "v6_new_failed_netstack_reports_exists")]
3326 #[test_case(
3327 RouteRequestKind::New,
3328 vec![],
3329 Err(RequestError::AlreadyExists),
3330 V4_SUB1,
3331 Some(METRIC1),
3332 DEV1;
3333 "v4_new_failed_netlink_reports_exists")]
3334 #[test_case(
3335 RouteRequestKind::New,
3336 vec![],
3337 Err(RequestError::AlreadyExists),
3338 V4_SUB1,
3339 Some(METRIC1),
3340 DEV2;
3341 "v4_new_failed_netlink_reports_exists_different_interface")]
3342 #[test_case(
3343 RouteRequestKind::New,
3344 vec![],
3345 Err(RequestError::AlreadyExists),
3346 V6_SUB1,
3347 Some(METRIC1),
3348 DEV1;
3349 "v6_new_failed_netlink_reports_exists")]
3350 #[test_case(
3351 RouteRequestKind::New,
3352 vec![],
3353 Err(RequestError::AlreadyExists),
3354 V6_SUB1,
3355 Some(METRIC1),
3356 DEV2;
3357 "v6_new_failed_netlink_reports_exists_different_interface")]
3358 #[test_case(
3359 RouteRequestKind::New,
3360 vec![
3361 RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3362 ],
3363 Err(RequestError::InvalidRequest),
3364 V4_SUB1,
3365 Some(METRIC3),
3366 DEV1;
3367 "v4_new_invalid_dest")]
3368 #[test_case(
3369 RouteRequestKind::New,
3370 vec![
3371 RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3372 ],
3373 Err(RequestError::InvalidRequest),
3374 V6_SUB1,
3375 Some(METRIC3),
3376 DEV1;
3377 "v6_new_invalid_dest")]
3378 #[test_case(
3379 RouteRequestKind::New,
3380 vec![
3381 RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3382 ],
3383 Err(RequestError::InvalidRequest),
3384 V4_SUB1,
3385 Some(METRIC3),
3386 DEV1;
3387 "v4_new_invalid_hop")]
3388 #[test_case(
3389 RouteRequestKind::New,
3390 vec![
3391 RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3392 ],
3393 Err(RequestError::InvalidRequest),
3394 V6_SUB1,
3395 Some(METRIC3),
3396 DEV1;
3397 "v6_new_invalid_hop")]
3398 #[test_case(
3400 RouteRequestKind::Del,
3401 vec![
3402 RouteSetResult::DelResult(Ok(true))
3403 ],
3404 Ok(()),
3405 V4_SUB1,
3406 None,
3407 DEV1;
3408 "v4_del_success_only_subnet")]
3409 #[test_case(
3410 RouteRequestKind::Del,
3411 vec![
3412 RouteSetResult::DelResult(Ok(true))
3413 ],
3414 Ok(()),
3415 V4_SUB1,
3416 Some(METRIC1),
3417 DEV1;
3418 "v4_del_success_only_subnet_metric")]
3419 #[test_case(
3420 RouteRequestKind::Del,
3421 vec![
3422 RouteSetResult::DelResult(Ok(true))
3423 ],
3424 Ok(()),
3425 V6_SUB1,
3426 None,
3427 DEV1;
3428 "v6_del_success_only_subnet")]
3429 #[test_case(
3430 RouteRequestKind::Del,
3431 vec![
3432 RouteSetResult::DelResult(Ok(true))
3433 ],
3434 Ok(()),
3435 V6_SUB1,
3436 Some(METRIC1),
3437 DEV1;
3438 "v6_del_success_only_subnet_metric")]
3439 #[test_case(
3440 RouteRequestKind::Del,
3441 vec![
3442 RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3443 RouteSetResult::AuthenticationResult(Err(
3444 fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3445 )),
3446 ],
3447 Err(RequestError::UnrecognizedInterface),
3448 V4_SUB1,
3449 None,
3450 DEV1;
3451 "v4_del_failed_auth")]
3452 #[test_case(
3453 RouteRequestKind::Del,
3454 vec![
3455 RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3456 RouteSetResult::AuthenticationResult(Err(
3457 fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3458 )),
3459 ],
3460 Err(RequestError::UnrecognizedInterface),
3461 V6_SUB1,
3462 None,
3463 DEV1;
3464 "v6_del_failed_auth")]
3465 #[test_case(
3466 RouteRequestKind::Del,
3467 vec![
3468 RouteSetResult::DelResult(Ok(false))
3469 ],
3470 Err(RequestError::DeletionNotAllowed),
3471 V4_SUB1,
3472 None,
3473 DEV1;
3474 "v4_del_failed_attempt_to_delete_route_from_global_set")]
3475 #[test_case(
3476 RouteRequestKind::Del,
3477 vec![
3478 RouteSetResult::DelResult(Ok(false))
3479 ],
3480 Err(RequestError::DeletionNotAllowed),
3481 V6_SUB1,
3482 None,
3483 DEV1;
3484 "v6_del_failed_attempt_to_delete_route_from_global_set")]
3485 #[test_case(
3491 RouteRequestKind::Del,
3492 vec![],
3493 Err(RequestError::NotFound),
3494 V4_SUB1,
3495 Some(METRIC3),
3496 DEV1;
3497 "v4_del_no_matching_route")]
3498 #[test_case(
3499 RouteRequestKind::Del,
3500 vec![],
3501 Err(RequestError::NotFound),
3502 V6_SUB1,
3503 Some(METRIC3),
3504 DEV1;
3505 "v6_del_no_matching_route")]
3506 #[test_case(
3507 RouteRequestKind::Del,
3508 vec![
3509 RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3510 ],
3511 Err(RequestError::InvalidRequest),
3512 V4_SUB1,
3513 None,
3514 DEV1;
3515 "v4_del_invalid_dest")]
3516 #[test_case(
3517 RouteRequestKind::Del,
3518 vec![
3519 RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3520 ],
3521 Err(RequestError::InvalidRequest),
3522 V6_SUB1,
3523 None,
3524 DEV1;
3525 "v6_del_invalid_dest")]
3526 #[test_case(
3527 RouteRequestKind::Del,
3528 vec![
3529 RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3530 ],
3531 Err(RequestError::InvalidRequest),
3532 V4_SUB1,
3533 None,
3534 DEV1;
3535 "v4_del_invalid_hop")]
3536 #[test_case(
3537 RouteRequestKind::Del,
3538 vec![
3539 RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3540 ],
3541 Err(RequestError::InvalidRequest),
3542 V6_SUB1,
3543 None,
3544 DEV1;
3545 "v6_del_invalid_hop")]
3546 #[fuchsia::test]
3547 async fn test_new_del_route<A: IpAddress>(
3548 kind: RouteRequestKind,
3549 route_set_results: Vec<RouteSetResult>,
3550 waiter_result: Result<(), RequestError>,
3551 subnet: Subnet<A>,
3552 metric: Option<u32>,
3553 interface_id: u32,
3554 ) where
3555 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3556 {
3557 let route_group = match A::Version::VERSION {
3558 IpVersion::V4 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3559 IpVersion::V6 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3560 };
3561
3562 let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3563
3564 let route_req_args = match kind {
3568 RouteRequestKind::New => {
3569 RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3571 subnet,
3572 next_hop,
3573 interface_id.into(),
3574 metric.expect("add cases should be Some"),
3575 MANAGED_ROUTE_TABLE_INDEX,
3576 )))
3577 }
3578 RouteRequestKind::Del => {
3579 RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3581 subnet,
3582 None,
3583 None,
3584 metric,
3585 MANAGED_ROUTE_TABLE_INDEX,
3586 )))
3587 }
3588 };
3589
3590 let messages = match waiter_result {
3593 Ok(()) => {
3594 let build_message = |table| {
3595 let route_message = create_netlink_route_message::<A::Version>(
3596 subnet.prefix(),
3597 table,
3598 create_nlas::<A::Version>(
3599 Some(subnet),
3600 Some(next_hop),
3601 DEV1,
3602 match kind {
3603 RouteRequestKind::New => metric.expect("add cases should be some"),
3604 RouteRequestKind::Del => METRIC1,
3609 },
3610 (table != MAIN_ROUTE_TABLE_INDEX).then_some(table.get()),
3611 ),
3612 );
3613 let netlink_message = match kind {
3614 RouteRequestKind::New => {
3615 route_message.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false)
3616 }
3617 RouteRequestKind::Del => route_message.into_rtnl_del_route(),
3618 };
3619 SentMessage::multicast(netlink_message, route_group)
3620 };
3621
3622 let route_message_in_managed_table = build_message(MANAGED_ROUTE_TABLE_INDEX);
3623
3624 vec![route_message_in_managed_table]
3625 }
3626 Err(_) => Vec::new(),
3627 };
3628
3629 test_route_requests_helper(
3630 [RequestArgs::Route(route_req_args)],
3631 messages,
3632 route_set_for_first_new_table(route_set_results),
3633 vec![waiter_result],
3634 subnet,
3635 )
3636 .await;
3637 }
3638
3639 #[test_case(
3642 RouteRequestKind::New,
3643 vec![
3644 RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3645 RouteSetResult::AuthenticationResult(Ok(())),
3646 RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3647 ],
3648 Err(RequestError::InvalidRequest),
3649 V4_SUB1;
3650 "v4_new_unauthenticated")]
3651 #[test_case(
3652 RouteRequestKind::New,
3653 vec![
3654 RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3655 RouteSetResult::AuthenticationResult(Ok(())),
3656 RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3657 ],
3658 Err(RequestError::InvalidRequest),
3659 V6_SUB1;
3660 "v6_new_unauthenticated")]
3661 #[test_case(
3662 RouteRequestKind::Del,
3663 vec![
3664 RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3665 RouteSetResult::AuthenticationResult(Ok(())),
3666 RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3667 ],
3668 Err(RequestError::InvalidRequest),
3669 V4_SUB1;
3670 "v4_del_unauthenticated")]
3671 #[test_case(
3672 RouteRequestKind::Del,
3673 vec![
3674 RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3675 RouteSetResult::AuthenticationResult(Ok(())),
3676 RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3677 ],
3678 Err(RequestError::InvalidRequest),
3679 V6_SUB1;
3680 "v6_del_unauthenticated")]
3681 #[should_panic(expected = "received unauthentication error from route set for route")]
3682 #[fuchsia::test]
3683 async fn test_new_del_route_failed<A: IpAddress>(
3684 kind: RouteRequestKind,
3685 route_set_results: Vec<RouteSetResult>,
3686 waiter_result: Result<(), RequestError>,
3687 subnet: Subnet<A>,
3688 ) where
3689 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3690 {
3691 let route_req_args = match kind {
3692 RouteRequestKind::New => {
3693 let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3694 RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3696 subnet,
3697 next_hop,
3698 DEV1.into(),
3699 METRIC3,
3700 MANAGED_ROUTE_TABLE_INDEX,
3701 )))
3702 }
3703 RouteRequestKind::Del => {
3704 RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3706 subnet,
3707 None,
3708 None,
3709 None,
3710 MANAGED_ROUTE_TABLE_INDEX,
3711 )))
3712 }
3713 };
3714 test_route_requests_helper(
3715 [RequestArgs::Route(route_req_args)],
3716 Vec::new(),
3717 route_set_for_first_new_table(route_set_results),
3718 vec![waiter_result],
3719 subnet,
3720 )
3721 .await;
3722 }
3723
3724 #[test_case(
3725 Err(RequestError::NotFound),
3726 V4_SUB1; "v4_del")]
3727 #[test_case(
3728 Err(RequestError::NotFound),
3729 V6_SUB1; "v6_del")]
3730 #[fuchsia::test]
3731 async fn test_del_route_nonexistent_table<A: IpAddress>(
3732 waiter_result: Result<(), RequestError>,
3733 subnet: Subnet<A>,
3734 ) where
3735 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3736 {
3737 let route_req_args =
3739 RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3740 subnet,
3741 None,
3742 None,
3743 None,
3744 NetlinkRouteTableIndex::new(1234),
3745 )));
3746 test_route_requests_helper(
3747 [RequestArgs::Route(route_req_args)],
3748 Vec::new(),
3749 HashMap::new(),
3750 vec![waiter_result],
3751 subnet,
3752 )
3753 .await;
3754 }
3755
3756 #[test_case(
3760 V4_SUB1,
3761 ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3762 MANAGED_ROUTE_TABLE_INDEX;
3763 "v4_new_same_table_dump")]
3764 #[test_case(
3765 V6_SUB1,
3766 ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3767 MANAGED_ROUTE_TABLE_INDEX;
3768 "v6_new_same_table_dump")]
3769 #[test_case(
3770 V4_SUB1,
3771 ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3772 NetlinkRouteTableIndex::new(1234);
3773 "v4_new_different_table_dump")]
3774 #[test_case(
3775 V6_SUB1,
3776 ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3777 NetlinkRouteTableIndex::new(1234);
3778 "v6_new_different_table_dump")]
3779 #[fuchsia::test]
3780 async fn test_new_then_get_dump_request<A: IpAddress>(
3781 subnet: Subnet<A>,
3782 group: ModernGroup,
3783 table: NetlinkRouteTableIndex,
3784 ) where
3785 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3786 {
3787 let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3788 (),
3789 |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3790 |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3791 );
3792
3793 let unicast_route_args =
3799 create_unicast_new_route_args(subnet, next_hop1, DEV1.into(), METRIC3, table);
3800
3801 let messages = vec![
3806 SentMessage::multicast(
3807 create_netlink_route_message::<A::Version>(
3808 subnet.prefix(),
3809 table,
3810 create_nlas::<A::Version>(
3811 Some(subnet),
3812 Some(next_hop1),
3813 DEV1,
3814 METRIC3,
3815 Some(table.get()),
3816 ),
3817 )
3818 .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3819 group,
3820 ),
3821 SentMessage::unicast(
3822 create_netlink_route_message::<A::Version>(
3823 subnet.prefix(),
3824 MANAGED_ROUTE_TABLE_INDEX,
3825 create_nlas::<A::Version>(
3826 Some(subnet),
3827 Some(next_hop1),
3828 DEV1,
3829 METRIC1,
3830 Some(MANAGED_ROUTE_TABLE_ID),
3831 ),
3832 )
3833 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3834 ),
3835 SentMessage::unicast(
3836 create_netlink_route_message::<A::Version>(
3837 subnet.prefix(),
3838 MANAGED_ROUTE_TABLE_INDEX,
3839 create_nlas::<A::Version>(
3840 Some(subnet),
3841 Some(next_hop2),
3842 DEV2,
3843 METRIC2,
3844 Some(MANAGED_ROUTE_TABLE_ID),
3845 ),
3846 )
3847 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3848 ),
3849 SentMessage::unicast(
3850 create_netlink_route_message::<A::Version>(
3851 subnet.prefix(),
3852 table,
3853 create_nlas::<A::Version>(
3854 Some(subnet),
3855 Some(next_hop1),
3856 DEV1,
3857 METRIC3,
3858 Some(table.get()),
3859 ),
3860 )
3861 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3862 ),
3863 ];
3864
3865 test_route_requests_helper(
3866 [
3867 RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
3868 unicast_route_args,
3869 ))),
3870 RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
3871 ],
3872 messages,
3873 route_set_for_table_id(
3874 vec![RouteSetResult::AddResult(Ok(true))],
3875 if table == MANAGED_ROUTE_TABLE_INDEX {
3876 OTHER_FIDL_TABLE_ID
3877 } else {
3878 fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1)
3879 },
3880 ),
3881 vec![Ok(()), Ok(())],
3882 subnet,
3883 )
3884 .await;
3885 }
3886
3887 #[test_case(V4_SUB1; "v4_new_dump")]
3896 #[test_case(V6_SUB1; "v6_new_dump")]
3897 #[fuchsia::test]
3898 async fn test_new_route_different_table_then_get_dump_request<A: IpAddress>(subnet: Subnet<A>)
3899 where
3900 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3901 {
3902 let (next_hop1, next_hop2, IpInvariant(group)): (A, A, IpInvariant<ModernGroup>) =
3903 A::Version::map_ip(
3904 (),
3905 |()| {
3906 (
3907 V4_NEXTHOP1,
3908 V4_NEXTHOP2,
3909 IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE)),
3910 )
3911 },
3912 |()| {
3913 (
3914 V6_NEXTHOP1,
3915 V6_NEXTHOP2,
3916 IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE)),
3917 )
3918 },
3919 );
3920
3921 const ALTERNATIVE_ROUTE_TABLE: NetlinkRouteTableIndex = NetlinkRouteTableIndex::new(1337);
3922
3923 let unicast_route_args = create_unicast_new_route_args(
3930 subnet,
3931 next_hop1,
3932 DEV1.into(),
3933 METRIC1,
3934 ALTERNATIVE_ROUTE_TABLE,
3935 );
3936
3937 let messages = vec![
3941 SentMessage::multicast(
3942 create_netlink_route_message::<A::Version>(
3943 subnet.prefix(),
3944 ALTERNATIVE_ROUTE_TABLE,
3945 create_nlas::<A::Version>(
3946 Some(subnet),
3947 Some(next_hop1),
3948 DEV1,
3949 METRIC1,
3950 Some(ALTERNATIVE_ROUTE_TABLE.get()),
3951 ),
3952 )
3953 .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3954 group,
3955 ),
3956 SentMessage::unicast(
3957 create_netlink_route_message::<A::Version>(
3958 subnet.prefix(),
3959 MANAGED_ROUTE_TABLE_INDEX,
3960 create_nlas::<A::Version>(
3961 Some(subnet),
3962 Some(next_hop1),
3963 DEV1,
3964 METRIC1,
3965 Some(MANAGED_ROUTE_TABLE_ID),
3966 ),
3967 )
3968 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3969 ),
3970 SentMessage::unicast(
3971 create_netlink_route_message::<A::Version>(
3972 subnet.prefix(),
3973 ALTERNATIVE_ROUTE_TABLE,
3974 create_nlas::<A::Version>(
3975 Some(subnet),
3976 Some(next_hop1),
3977 DEV1,
3978 METRIC1,
3979 Some(ALTERNATIVE_ROUTE_TABLE.get()),
3980 ),
3981 )
3982 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3983 ),
3984 SentMessage::unicast(
3985 create_netlink_route_message::<A::Version>(
3986 subnet.prefix(),
3987 MANAGED_ROUTE_TABLE_INDEX,
3988 create_nlas::<A::Version>(
3989 Some(subnet),
3990 Some(next_hop2),
3991 DEV2,
3992 METRIC2,
3993 Some(MANAGED_ROUTE_TABLE_ID),
3994 ),
3995 )
3996 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3997 ),
3998 ];
3999
4000 test_route_requests_helper(
4001 [
4002 RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
4003 unicast_route_args,
4004 ))),
4005 RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
4006 ],
4007 messages,
4008 HashMap::from_iter([
4009 (MAIN_FIDL_TABLE_ID, vec![RouteSetResult::AddResult(Ok(false))].into()),
4011 (
4013 fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1),
4014 vec![RouteSetResult::AddResult(Ok(true))].into(),
4015 ),
4016 ]),
4017 vec![Ok(()), Ok(())],
4018 subnet,
4019 )
4020 .await;
4021 }
4022
4023 #[test_case(V4_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE); "v4_new_del_dump")]
4027 #[test_case(V6_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE); "v6_new_del_dump")]
4028 #[fuchsia::test]
4029 async fn test_new_then_del_then_get_dump_request<A: IpAddress>(
4030 subnet: Subnet<A>,
4031 group: ModernGroup,
4032 ) where
4033 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
4034 {
4035 let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
4036 (),
4037 |()| (V4_NEXTHOP1, V4_NEXTHOP2),
4038 |()| (V6_NEXTHOP1, V6_NEXTHOP2),
4039 );
4040
4041 let new_route_args = create_unicast_new_route_args(
4047 subnet,
4048 next_hop1,
4049 DEV1.into(),
4050 METRIC3,
4051 MANAGED_ROUTE_TABLE_INDEX,
4052 );
4053
4054 let del_route_args = create_unicast_del_route_args(
4056 subnet,
4057 None,
4058 None,
4059 Some(METRIC3),
4060 MANAGED_ROUTE_TABLE_INDEX,
4061 );
4062
4063 let messages = vec![
4067 SentMessage::multicast(
4068 create_netlink_route_message::<A::Version>(
4069 subnet.prefix(),
4070 MANAGED_ROUTE_TABLE_INDEX,
4071 create_nlas::<A::Version>(
4072 Some(subnet),
4073 Some(next_hop1),
4074 DEV1,
4075 METRIC3,
4076 Some(MANAGED_ROUTE_TABLE_ID),
4077 ),
4078 )
4079 .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
4080 group,
4081 ),
4082 SentMessage::multicast(
4083 create_netlink_route_message::<A::Version>(
4084 subnet.prefix(),
4085 MANAGED_ROUTE_TABLE_INDEX,
4086 create_nlas::<A::Version>(
4087 Some(subnet),
4088 Some(next_hop1),
4089 DEV1,
4090 METRIC3,
4091 Some(MANAGED_ROUTE_TABLE_ID),
4092 ),
4093 )
4094 .into_rtnl_del_route(),
4095 group,
4096 ),
4097 SentMessage::unicast(
4098 create_netlink_route_message::<A::Version>(
4099 subnet.prefix(),
4100 MANAGED_ROUTE_TABLE_INDEX,
4101 create_nlas::<A::Version>(
4102 Some(subnet),
4103 Some(next_hop1),
4104 DEV1,
4105 METRIC1,
4106 Some(MANAGED_ROUTE_TABLE_ID),
4107 ),
4108 )
4109 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4110 ),
4111 SentMessage::unicast(
4112 create_netlink_route_message::<A::Version>(
4113 subnet.prefix(),
4114 MANAGED_ROUTE_TABLE_INDEX,
4115 create_nlas::<A::Version>(
4116 Some(subnet),
4117 Some(next_hop2),
4118 DEV2,
4119 METRIC2,
4120 Some(MANAGED_ROUTE_TABLE_ID),
4121 ),
4122 )
4123 .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4124 ),
4125 ];
4126
4127 test_route_requests_helper(
4128 [
4129 RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(new_route_args))),
4130 RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(del_route_args))),
4131 RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
4132 ],
4133 messages,
4134 route_set_for_first_new_table(vec![
4135 RouteSetResult::AddResult(Ok(true)),
4136 RouteSetResult::DelResult(Ok(true)),
4137 ]),
4138 vec![Ok(()), Ok(()), Ok(())],
4139 subnet,
4140 )
4141 .await;
4142 }
4143
4144 #[test_case(RouteRequestKind::New, V4_SUB1; "v4_new_if_removed")]
4149 #[test_case(RouteRequestKind::New, V6_SUB1; "v6_new_if_removed")]
4150 #[test_case(RouteRequestKind::Del, V4_SUB1; "v4_del_if_removed")]
4151 #[test_case(RouteRequestKind::Del, V6_SUB1; "v6_del_if_removed")]
4152 #[fuchsia::test]
4153 async fn test_new_del_route_interface_removed<A: IpAddress>(
4154 kind: RouteRequestKind,
4155 subnet: Subnet<A>,
4156 ) where
4157 A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
4158 {
4159 let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
4160 (),
4161 |()| (V4_NEXTHOP1, V4_NEXTHOP2),
4162 |()| (V6_NEXTHOP1, V6_NEXTHOP2),
4163 );
4164
4165 let (route_req_args, route_set_result) = match kind {
4169 RouteRequestKind::New => {
4170 let args =
4172 RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
4173 subnet,
4174 next_hop1,
4175 DEV1.into(),
4176 METRIC3,
4177 MANAGED_ROUTE_TABLE_INDEX,
4178 )));
4179 let res = RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated));
4180 (args, res)
4181 }
4182 RouteRequestKind::Del => {
4183 let args =
4185 RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
4186 subnet,
4187 None,
4188 None,
4189 None,
4190 MANAGED_ROUTE_TABLE_INDEX,
4191 )));
4192 let res = RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated));
4193 (args, res)
4194 }
4195 };
4196
4197 let expected_messages = Vec::new();
4199
4200 pretty_assertions::assert_eq!(
4201 test_requests(
4202 [RequestArgs::Route(route_req_args)],
4203 |interfaces_request_stream| async move {
4204 interfaces_request_stream
4205 .for_each(|req| {
4206 futures::future::ready(match req.unwrap() {
4207 fnet_root::InterfacesRequest::GetAdmin {
4208 id,
4209 control,
4210 control_handle: _,
4211 } => {
4212 pretty_assertions::assert_eq!(id, DEV1 as u64);
4213 let control = control.into_stream();
4214 let control = control.control_handle();
4215 control.shutdown();
4216 }
4217 req => unreachable!("unexpected interfaces request: {req:?}"),
4218 })
4219 })
4220 .await
4221 },
4222 route_set_for_first_new_table(vec![route_set_result]),
4223 subnet,
4224 next_hop1,
4225 next_hop2,
4226 expected_messages.len(),
4227 )
4228 .await,
4229 TestRequestResult {
4230 messages: expected_messages,
4231 waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
4232 },
4233 )
4234 }
4235
4236 #[derive(Clone)]
4238 struct Route<I: Ip> {
4239 subnet: Subnet<I::Addr>,
4240 device: u32,
4241 nexthop: Option<I::Addr>,
4242 metric: Option<NonZeroU32>,
4243 }
4244
4245 impl<I: Ip> Route<I> {
4246 fn to_route(self) -> fnet_routes_ext::Route<I> {
4247 let Self { subnet, device, nexthop, metric } = self;
4248 fnet_routes_ext::Route {
4249 destination: subnet,
4250 action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget {
4251 outbound_interface: device.into(),
4252 next_hop: nexthop
4253 .map(|a| SpecifiedAddr::new(a).expect("nexthop should be specified")),
4254 }),
4255 properties: fnet_routes_ext::RouteProperties {
4256 specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
4257 metric: netlink_priority_to_specified_metric(metric, I::VERSION),
4258 },
4259 },
4260 }
4261 }
4262
4263 fn to_installed_route(
4264 self,
4265 table_id: fnet_routes_ext::TableId,
4266 ) -> fnet_routes_ext::InstalledRoute<I> {
4267 const DEFAULT_INTERFACE_METRIC: u32 = 100000;
4268 let effective_metric = match self.metric {
4269 None => DEFAULT_INTERFACE_METRIC,
4270 Some(metric) => metric.into(),
4271 };
4272 let route = self.to_route();
4273 fnet_routes_ext::InstalledRoute {
4274 route,
4275 effective_properties: fnet_routes_ext::EffectiveRouteProperties {
4276 metric: effective_metric,
4277 },
4278 table_id,
4279 }
4280 }
4281 }
4282
4283 const ROUTE_METRIC1: NonZeroU32 = NonZeroU32::new(METRIC1).unwrap();
4284 const ROUTE_METRIC2: NonZeroU32 = NonZeroU32::new(METRIC2).unwrap();
4285 const ROUTE_METRIC3: NonZeroU32 = NonZeroU32::new(METRIC3).unwrap();
4286
4287 #[test_case(
4288 Route::<Ipv4>{
4289 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4290 },
4291 Route::<Ipv4>{
4292 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4293 },
4294 true; "all_fields_the_same_v4_should_match")]
4295 #[test_case(
4296 Route::<Ipv6>{
4297 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4298 },
4299 Route::<Ipv6>{
4300 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4301 },
4302 true; "all_fields_the_same_v6_should_match")]
4303 #[test_case(
4304 Route::<Ipv4>{
4305 subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4306 },
4307 Route::<Ipv4>{
4308 subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4309 },
4310 true; "default_route_v4_should_match")]
4311 #[test_case(
4312 Route::<Ipv6>{
4313 subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4314 },
4315 Route::<Ipv6>{
4316 subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4317 },
4318 true; "default_route_v6_should_match")]
4319 #[test_case(
4320 Route::<Ipv4>{
4321 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4322 },
4323 Route::<Ipv4>{
4324 subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4325 },
4326 true; "different_device_v4_should_match")]
4327 #[test_case(
4328 Route::<Ipv6>{
4329 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4330 },
4331 Route::<Ipv6>{
4332 subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4333 },
4334 true; "different_device_v6_should_match")]
4335 #[test_case(
4336 Route::<Ipv4>{
4337 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4338 },
4339 Route::<Ipv4>{
4340 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4341 },
4342 true; "different_nexthop_v4_should_match")]
4343 #[test_case(
4344 Route::<Ipv6>{
4345 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4346 },
4347 Route::<Ipv6>{
4348 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4349 },
4350 true; "different_nexthop_v6_should_match")]
4351 #[test_case(
4352 Route::<Ipv4>{
4353 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4354 },
4355 Route::<Ipv4>{
4356 subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4357 },
4358 true; "different_device_and_nexthop_v4_should_match")]
4359 #[test_case(
4360 Route::<Ipv6>{
4361 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4362 },
4363 Route::<Ipv6>{
4364 subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4365 },
4366 true; "different_device_and_nexthop_v6_should_match")]
4367 #[test_case(
4368 Route::<Ipv4>{
4369 subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4370 },
4371 Route::<Ipv4>{
4372 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4373 },
4374 true; "nexthop_newly_unset_v4_should_match")]
4375 #[test_case(
4376 Route::<Ipv6>{
4377 subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4378 },
4379 Route::<Ipv6>{
4380 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4381 },
4382 true; "nexthop_newly_unset_v6_should_match")]
4383 #[test_case(
4384 Route::<Ipv4>{
4385 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4386 },
4387 Route::<Ipv4>{
4388 subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4389 },
4390 true; "nexthop_previously_unset_v4_should_match")]
4391 #[test_case(
4392 Route::<Ipv6>{
4393 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4394 },
4395 Route::<Ipv6>{
4396 subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4397 },
4398 true; "nexthop_previously_unset_v6_should_match")]
4399 #[test_case(
4400 Route::<Ipv4>{
4401 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4402 },
4403 Route::<Ipv4>{
4404 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4405 },
4406 false; "different_metric_v4_should_not_match")]
4407 #[test_case(
4408 Route::<Ipv4>{
4409 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4410 },
4411 Route::<Ipv4>{
4412 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4413 },
4414 false; "default_and_non_default_v4_should_not_match")]
4415 #[test_case(
4416 Route::<Ipv4>{
4417 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4418 },
4419 Route::<Ipv4>{
4420 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4421 },
4422 true; "default_and_default_v4_should_match")]
4423 #[test_case(
4424 Route::<Ipv6>{
4425 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4426 },
4427 Route::<Ipv6>{
4428 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4429 },
4430 false; "different_metric_v6_should_not_match")]
4431 #[test_case(
4432 Route::<Ipv4>{
4433 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4434 },
4435 Route::<Ipv4>{
4436 subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4437 },
4438 false; "different_subnet_v4_should_not_match")]
4439 #[test_case(
4440 Route::<Ipv6>{
4441 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4442 },
4443 Route::<Ipv6>{
4444 subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4445 },
4446 false; "different_subnet_v6_should_not_match")]
4447 #[test_case(
4448 Route::<Ipv4>{
4449 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4450 },
4451 Route::<Ipv4>{
4452 subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4453 },
4454 false; "different_subnet_prefixlen_v4_should_not_match")]
4455 #[test_case(
4456 Route::<Ipv6>{
4457 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4458 },
4459 Route::<Ipv6>{
4460 subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4461 },
4462 false; "different_subnet_prefixlen_v6_should_not_match")]
4463 #[test_case(
4464 Route::<Ipv6>{
4465 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4466 },
4467 Route::<Ipv6>{
4468 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4469 },
4470 false; "default_and_non_default_v6_should_not_match")]
4471 #[test_case(
4472 Route::<Ipv6>{
4473 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4474 },
4475 Route::<Ipv6>{
4476 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4477 },
4478 true; "default_and_default_v6_should_match")]
4479 fn test_new_route_matcher<I: Ip>(
4480 route1: Route<I>,
4481 route2: Route<I>,
4482 expected_to_conflict: bool,
4483 ) {
4484 let route1 = route1.to_installed_route(MAIN_FIDL_TABLE_ID);
4485 let route2 = route2.to_installed_route(MAIN_FIDL_TABLE_ID);
4486
4487 let got_conflict = routes_conflict::<I>(route1, route2.route, route2.table_id);
4488 assert_eq!(got_conflict, expected_to_conflict);
4489
4490 let got_conflict = routes_conflict::<I>(route2, route1.route, route1.table_id);
4491 assert_eq!(got_conflict, expected_to_conflict);
4492 }
4493
4494 fn test_select_route_for_deletion_helper<
4499 I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4500 >(
4501 args: UnicastDelRouteArgs<I>,
4502 existing_routes: &[Route<I>],
4503 expected_index: Option<usize>,
4505 ) {
4506 let mut fidl_route_map = FidlRouteMap::<I>::default();
4507
4508 let _executor = fuchsia_async::TestExecutor::new();
4511
4512 let (main_route_table_proxy, _server_end) =
4513 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4514 let (own_route_table_proxy, _server_end) =
4515 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4516 let (route_set_proxy, _server_end) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4517 let (unmanaged_route_set_proxy, _unmanaged_route_set_server_end) =
4518 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4519 let (route_table_provider, _server_end) =
4520 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
4521
4522 let mut route_table_map = RouteTableMap::<I>::new(
4523 main_route_table_proxy,
4524 MAIN_FIDL_TABLE_ID,
4525 unmanaged_route_set_proxy,
4526 route_table_provider,
4527 );
4528
4529 route_table_map.insert(
4530 MANAGED_ROUTE_TABLE_INDEX,
4531 RouteTable::Managed(ManagedRouteTable {
4532 route_table_proxy: own_route_table_proxy,
4533 route_set_proxy,
4534 fidl_table_id: OTHER_FIDL_TABLE_ID,
4535 rule_set_authenticated: false,
4536 }),
4537 );
4538
4539 for Route { subnet, device, nexthop, metric } in existing_routes {
4540 let fnet_routes_ext::InstalledRoute { route, effective_properties, table_id } =
4541 create_installed_route::<I>(
4542 *subnet,
4543 *nexthop,
4544 (*device).into(),
4545 metric.map_or(0, NonZeroU32::get),
4546 OTHER_FIDL_TABLE_ID,
4547 );
4548 assert_matches!(fidl_route_map.add(route, table_id, effective_properties), None);
4549 }
4550
4551 let existing_routes = existing_routes
4552 .iter()
4553 .map(|Route { subnet, device, nexthop, metric }| {
4554 let destination = (subnet.prefix() != 0).then_some(*subnet);
4556 create_netlink_route_message::<I>(
4557 subnet.prefix(),
4558 MANAGED_ROUTE_TABLE_INDEX,
4559 create_nlas::<I>(
4560 destination,
4561 nexthop.to_owned(),
4562 *device,
4563 metric.map_or(0, NonZeroU32::get),
4564 Some(MANAGED_ROUTE_TABLE_ID),
4565 ),
4566 )
4567 })
4568 .collect::<Vec<_>>();
4569 let expected_route = expected_index.map(|index| {
4570 existing_routes
4571 .get(index)
4572 .expect("index should be within the bounds of `existing_routes`")
4573 .clone()
4574 });
4575
4576 assert_eq!(
4577 select_route_for_deletion(
4578 &fidl_route_map,
4579 &route_table_map,
4580 DelRouteArgs::Unicast(args).try_into().unwrap(),
4581 ),
4582 expected_route
4583 )
4584 }
4585
4586 #[test_case(
4587 UnicastDelRouteArgs::<Ipv4> {
4588 subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4589 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4590 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4591 ),
4592 },
4593 Route::<Ipv4>{
4594 subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4595 },
4596 false; "subnet_does_not_match_v4")]
4597 #[test_case(
4598 UnicastDelRouteArgs::<Ipv4> {
4599 subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4600 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4601 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4602 ),
4603 },
4604 Route::<Ipv4>{
4605 subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4606 },
4607 false; "subnet_prefix_len_does_not_match_v4")]
4608 #[test_case(
4609 UnicastDelRouteArgs::<Ipv4> {
4610 subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4611 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4612 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4613 ),
4614 },
4615 Route::<Ipv4>{
4616 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4617 },
4618 true; "subnet_matches_v4")]
4619 #[test_case(
4620 UnicastDelRouteArgs::<Ipv4> {
4621 subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4622 next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4623 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4624 ),
4625 },
4626 Route::<Ipv4>{
4627 subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4628 },
4629 false; "interface_does_not_match_v4")]
4630 #[test_case(
4631 UnicastDelRouteArgs::<Ipv4> {
4632 subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4633 next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4634 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4635 ),
4636 },
4637 Route::<Ipv4>{
4638 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4639 },
4640 true; "interface_matches_v4")]
4641 #[test_case(
4642 UnicastDelRouteArgs::<Ipv4> {
4643 subnet: V4_SUB1, outbound_interface: None,
4644 next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4645 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4646 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4647 ),
4648 },
4649 Route::<Ipv4>{
4650 subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4651 },
4652 false; "nexthop_absent_v4")]
4653 #[test_case(
4654 UnicastDelRouteArgs::<Ipv4> {
4655 subnet: V4_SUB1, outbound_interface: None,
4656 next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4657 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4658 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4659 ),
4660 },
4661 Route::<Ipv4>{
4662 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4663 },
4664 false; "nexthop_does_not_match_v4")]
4665 #[test_case(
4666 UnicastDelRouteArgs::<Ipv4> {
4667 subnet: V4_SUB1, outbound_interface: None,
4668 next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4669 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4670 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4671 ),
4672 },
4673 Route::<Ipv4>{
4674 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4675 },
4676 true; "nexthop_matches_v4")]
4677 #[test_case(
4678 UnicastDelRouteArgs::<Ipv4> {
4679 subnet: V4_SUB1, outbound_interface: None,
4680 next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4681 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4682 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4683 ),
4684 },
4685 Route::<Ipv4>{
4686 subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4687 },
4688 false; "metric_does_not_match_v4")]
4689 #[test_case(
4690 UnicastDelRouteArgs::<Ipv4> {
4691 subnet: V4_SUB1, outbound_interface: None,
4692 next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4693 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4694 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4695 ),
4696 },
4697 Route::<Ipv4>{
4698 subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4699 },
4700 true; "metric_matches_v4")]
4701 #[test_case(
4702 UnicastDelRouteArgs::<Ipv6> {
4703 subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4704 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4705 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4706 ),
4707 },
4708 Route::<Ipv6>{
4709 subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4710 },
4711 false; "subnet_does_not_match_v6")]
4712 #[test_case(
4713 UnicastDelRouteArgs::<Ipv6> {
4714 subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4715 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4716 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4717 ),
4718 },
4719 Route::<Ipv6>{
4720 subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4721 },
4722 false; "subnet_prefix_len_does_not_match_v6")]
4723 #[test_case(
4724 UnicastDelRouteArgs::<Ipv6> {
4725 subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4726 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4727 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4728 ),
4729 },
4730 Route::<Ipv6>{
4731 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4732 },
4733 true; "subnet_matches_v6")]
4734 #[test_case(
4735 UnicastDelRouteArgs::<Ipv6> {
4736 subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4737 next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4738 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4739 ),
4740 },
4741 Route::<Ipv6>{
4742 subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4743 },
4744 false; "interface_does_not_match_v6")]
4745 #[test_case(
4746 UnicastDelRouteArgs::<Ipv6> {
4747 subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4748 next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4749 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4750 ),
4751 },
4752 Route::<Ipv6>{
4753 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4754 },
4755 true; "interface_matches_v6")]
4756 #[test_case(
4757 UnicastDelRouteArgs::<Ipv6> {
4758 subnet: V6_SUB1, outbound_interface: None,
4759 next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4760 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4761 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4762 ),
4763 },
4764 Route::<Ipv6>{
4765 subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4766 },
4767 false; "nexthop_absent_v6")]
4768 #[test_case(
4769 UnicastDelRouteArgs::<Ipv6> {
4770 subnet: V6_SUB1, outbound_interface: None,
4771 next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4772 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4773 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4774 ),
4775 },
4776 Route::<Ipv6>{
4777 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4778 },
4779 false; "nexthop_does_not_match_v6")]
4780 #[test_case(
4781 UnicastDelRouteArgs::<Ipv6> {
4782 subnet: V6_SUB1, outbound_interface: None,
4783 next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4784 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4785 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4786 ),
4787 },
4788 Route::<Ipv6>{
4789 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4790 },
4791 true; "nexthop_matches_v6")]
4792 #[test_case(
4793 UnicastDelRouteArgs::<Ipv6> {
4794 subnet: V6_SUB1, outbound_interface: None,
4795 next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4796 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4797 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4798 ),
4799 },
4800 Route::<Ipv6>{
4801 subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4802 },
4803 false; "metric_does_not_match_v6")]
4804 #[test_case(
4805 UnicastDelRouteArgs::<Ipv6> {
4806 subnet: V6_SUB1, outbound_interface: None,
4807 next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4808 table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4809 NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4810 ),
4811 },
4812 Route::<Ipv6>{
4813 subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4814 },
4815 true; "metric_matches_v6")]
4816 fn test_select_route_for_deletion<
4817 I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4818 >(
4819 args: UnicastDelRouteArgs<I>,
4820 existing_route: Route<I>,
4821 expect_match: bool,
4822 ) {
4823 test_select_route_for_deletion_helper(args, &[existing_route], expect_match.then_some(0))
4824 }
4825
4826 #[test_case(
4827 UnicastDelRouteArgs::<Ipv4> {
4828 subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4829 table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4830 },
4831 &[
4832 Route::<Ipv4>{
4833 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4834 },
4835 Route::<Ipv4>{
4836 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4837 },
4838 Route::<Ipv4>{
4839 subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4840 },
4841 ],
4842 Some(1); "multiple_matches_prefers_lowest_metric_v4")]
4843 #[test_case(
4844 UnicastDelRouteArgs::<Ipv6> {
4845 subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4846 table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4847 },
4848 &[
4849 Route::<Ipv6>{
4850 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4851 },
4852 Route::<Ipv6>{
4853 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4854 },
4855 Route::<Ipv6>{
4856 subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4857 },
4858 ],
4859 Some(1); "multiple_matches_prefers_lowest_metric_v6")]
4860 fn test_select_route_for_deletion_multiple_matches<
4861 I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4862 >(
4863 args: UnicastDelRouteArgs<I>,
4864 existing_routes: &[Route<I>],
4865 expected_index: Option<usize>,
4866 ) {
4867 test_select_route_for_deletion_helper(args, existing_routes, expected_index);
4868 }
4869
4870 #[ip_test(I, test = false)]
4871 #[fuchsia::test]
4872 async fn garbage_collects_empty_table<
4873 I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4874 >() {
4875 let (_route_sink, route_client, async_work_drain_task) =
4876 crate::client::testutil::new_fake_client::<NetlinkRoute>(
4877 crate::client::testutil::CLIENT_ID_1,
4878 [ModernGroup(match I::VERSION {
4879 IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
4880 IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
4881 })],
4882 );
4883 let join_handle = fasync::Task::spawn(async_work_drain_task);
4884 {
4885 let route_client = route_client;
4887 let route_clients = ClientTable::default();
4888 route_clients.add_client(route_client.clone());
4889
4890 let Setup {
4891 event_loop_inputs,
4892 watcher_stream,
4893 route_sets: (main_route_table_server_end, route_table_provider_server_end),
4894 interfaces_request_stream: _,
4895 mut request_sink,
4896 async_work_sink: _,
4897 } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
4898
4899 let mut main_route_table_fut = pin!(
4900 fnet_routes_ext::testutil::admin::serve_noop_route_sets_with_table_id::<I>(
4901 main_route_table_server_end,
4902 MAIN_FIDL_TABLE_ID
4903 )
4904 .fuse()
4905 );
4906
4907 let mut watcher_stream = pin!(watcher_stream.fuse());
4908 let mut route_table_provider_stream = route_table_provider_server_end.into_stream();
4909
4910 let mut event_loop = {
4911 let included_workers = match I::VERSION {
4912 IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
4913 routes_v4: EventLoopComponent::Present(()),
4914 routes_v6: EventLoopComponent::Absent(Optional),
4915 interfaces: EventLoopComponent::Absent(Optional),
4916 rules_v4: EventLoopComponent::Absent(Optional),
4917 rules_v6: EventLoopComponent::Absent(Optional),
4918 nduseropt: EventLoopComponent::Absent(Optional),
4919 neighbors: EventLoopComponent::Absent(Optional),
4920 },
4921 IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
4922 routes_v4: EventLoopComponent::Absent(Optional),
4923 routes_v6: EventLoopComponent::Present(()),
4924 interfaces: EventLoopComponent::Absent(Optional),
4925 rules_v4: EventLoopComponent::Absent(Optional),
4926 rules_v6: EventLoopComponent::Absent(Optional),
4927 nduseropt: EventLoopComponent::Absent(Optional),
4928 neighbors: EventLoopComponent::Absent(Optional),
4929 },
4930 };
4931
4932 let event_loop_fut = event_loop_inputs.initialize(included_workers).fuse();
4933 let watcher_fut = async {
4934 let watch_req =
4935 watcher_stream.by_ref().next().await.expect("should not have ended");
4936 fnet_routes_ext::testutil::handle_watch::<I>(
4938 watch_req,
4939 vec![fnet_routes_ext::Event::<I>::Idle.try_into().unwrap()],
4940 )
4941 }
4942 .fuse();
4943
4944 futures::select! {
4945 () = main_route_table_fut => unreachable!(),
4946 (event_loop, ()) = futures::future::join(
4947 event_loop_fut, watcher_fut
4948 ) => event_loop,
4949 }
4950 };
4951
4952 let (completer, mut initial_add_request_waiter) = oneshot::channel();
4953
4954 let new_route_args = NewRouteArgs::Unicast(I::map_ip_out(
4955 (),
4956 |()| {
4957 create_unicast_new_route_args(
4958 V4_SUB1,
4959 V4_NEXTHOP1,
4960 DEV1.into(),
4961 METRIC1,
4962 MANAGED_ROUTE_TABLE_INDEX,
4963 )
4964 },
4965 |()| {
4966 create_unicast_new_route_args(
4967 V6_SUB1,
4968 V6_NEXTHOP1,
4969 DEV1.into(),
4970 METRIC1,
4971 MANAGED_ROUTE_TABLE_INDEX,
4972 )
4973 },
4974 ));
4975 let expected_route = fnet_routes_ext::Route::<I>::from(new_route_args);
4976
4977 request_sink
4979 .try_send(
4980 Request {
4981 args: RequestArgs::Route(RouteRequestArgs::New(new_route_args)),
4982 sequence_number: TEST_SEQUENCE_NUMBER,
4983 client: route_client.clone(),
4984 completer,
4985 }
4986 .into(),
4987 )
4988 .expect("should succeed");
4989
4990 let (mut route_table_stream, mut route_set_stream) = {
4993 let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
4994 let route_table_fut = async {
4995 let server_end = match I::into_route_table_provider_request(
4996 route_table_provider_stream
4997 .try_next()
4998 .await
4999 .expect("should not have ended")
5000 .expect("fidl error"),
5001 ) {
5002 fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
5003 provider,
5004 options: _,
5005 control_handle: _,
5006 } => provider,
5007 r => panic!("unexpected request {r:?}"),
5008 };
5009 let mut route_table_stream = server_end.into_stream().boxed().fuse();
5010
5011 let request = I::into_route_table_request_result(
5012 route_table_stream.by_ref().next().await.expect("should not have ended"),
5013 )
5014 .expect("should not get error");
5015
5016 let responder = match request {
5017 RouteTableRequest::GetTableId { responder } => responder,
5018 _ => panic!("should be GetTableId"),
5019 };
5020 responder.send(OTHER_FIDL_TABLE_ID.get()).expect("should succeed");
5021
5022 let request = I::into_route_table_request_result(
5023 route_table_stream.by_ref().next().await.expect("should not have ended"),
5024 )
5025 .expect("should not get error");
5026
5027 let server_end = match request {
5028 RouteTableRequest::NewRouteSet { route_set, control_handle: _ } => {
5029 route_set
5030 }
5031 _ => panic!("should be NewRouteSet"),
5032 };
5033 let mut route_set_stream = server_end.into_stream().boxed().fuse();
5034
5035 let request = I::into_route_set_request_result(
5036 route_set_stream.by_ref().next().await.expect("should not have ended"),
5037 )
5038 .expect("should not get error");
5039
5040 let (route, responder) = match request {
5041 RouteSetRequest::AddRoute { route, responder } => (route, responder),
5042 _ => panic!("should be AddRoute"),
5043 };
5044 let route = route.expect("should successfully convert FIDl");
5045 assert_eq!(route, expected_route);
5046
5047 responder.send(Ok(true)).expect("sending response should succeed");
5048 (route_table_stream, route_set_stream)
5049 }
5050 .fuse();
5051 futures::select! {
5052 () = main_route_table_fut => unreachable!(),
5053 ((), streams) = futures::future::join(event_loop_fut, route_table_fut) => {
5054 streams
5055 }
5056 }
5057 };
5058
5059 {
5060 let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5061 let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
5063 Some(RouteTable::Managed(table)) => table,
5064 _ => panic!("table should be present"),
5065 };
5066 assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
5067
5068 assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
5071 &expected_route,
5072 [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5073 ));
5074 }
5075
5076 assert_matches!(initial_add_request_waiter.try_recv(), Ok(None));
5078
5079 {
5081 let event_loop_fut = async {
5082 event_loop.run_one_step_in_tests().await;
5084 event_loop.run_one_step_in_tests().await;
5085 }
5086 .fuse();
5087 let watcher_fut = async {
5088 let watch_req =
5089 watcher_stream.by_ref().next().await.expect("should not have ended");
5090 fnet_routes_ext::testutil::handle_watch::<I>(
5092 watch_req,
5093 vec![
5094 fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5095 route: expected_route,
5096 effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5097 metric: METRIC1,
5098 },
5099 table_id: MAIN_FIDL_TABLE_ID,
5100 })
5101 .try_into()
5102 .unwrap(),
5103 fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5104 route: expected_route,
5105 effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5106 metric: METRIC1,
5107 },
5108 table_id: OTHER_FIDL_TABLE_ID,
5109 })
5110 .try_into()
5111 .unwrap(),
5112 ],
5113 );
5114 };
5115 let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5116 }
5117
5118 {
5119 let (routes_worker, _route_table_map) = event_loop.route_table_state::<I>();
5120
5121 assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5123 &expected_route,
5124 [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5125 ));
5126 }
5127
5128 assert_matches!(initial_add_request_waiter.try_recv(), Ok(Some(Ok(()))));
5129
5130 let (completer, mut del_request_waiter) = oneshot::channel();
5131
5132 let del_route_args = I::map_ip_out(
5133 (),
5134 |()| {
5135 create_unicast_del_route_args(
5136 V4_SUB1,
5137 Some(V4_NEXTHOP1),
5138 Some(DEV1.into()),
5139 Some(METRIC1),
5140 MANAGED_ROUTE_TABLE_INDEX,
5141 )
5142 },
5143 |()| {
5144 create_unicast_del_route_args(
5145 V6_SUB1,
5146 Some(V6_NEXTHOP1),
5147 Some(DEV1.into()),
5148 Some(METRIC1),
5149 MANAGED_ROUTE_TABLE_INDEX,
5150 )
5151 },
5152 );
5153
5154 request_sink
5156 .try_send(
5157 Request {
5158 args: RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(
5159 del_route_args,
5160 ))),
5161 sequence_number: TEST_SEQUENCE_NUMBER,
5162 client: route_client.clone(),
5163 completer,
5164 }
5165 .into(),
5166 )
5167 .expect("should succeed");
5168
5169 {
5171 let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
5172 let route_set_fut = async {
5173 let request = I::into_route_set_request_result(
5174 route_set_stream.next().await.expect("should not have ended"),
5175 )
5176 .expect("should not get error");
5177 let (route, responder) = match request {
5178 RouteSetRequest::RemoveRoute { route, responder } => (route, responder),
5179 _ => panic!("should be DelRoute"),
5180 };
5181 let route = route.expect("should successfully convert FIDl");
5182 assert_eq!(route, expected_route);
5183
5184 responder.send(Ok(true)).expect("sending response should succeed");
5185 }
5186 .fuse();
5187
5188 futures::select! {
5189 () = main_route_table_fut => unreachable!(),
5190 ((), ()) = futures::future::join(event_loop_fut, route_set_fut) => (),
5191 }
5192 }
5193
5194 {
5196 let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5197 let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
5199 Some(RouteTable::Managed(table)) => table,
5200 _ => panic!("table should be present"),
5201 };
5202 assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
5203
5204 assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5205 &expected_route,
5206 [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5207 ));
5208 }
5209 assert_matches!(del_request_waiter.try_recv(), Ok(None));
5210
5211 {
5213 let event_loop_fut = async {
5214 event_loop.run_one_step_in_tests().await;
5216 event_loop.run_one_step_in_tests().await;
5217 }
5218 .fuse();
5219 let watcher_fut = async {
5220 let watch_req =
5221 watcher_stream.by_ref().next().await.expect("should not have ended");
5222 fnet_routes_ext::testutil::handle_watch::<I>(
5224 watch_req,
5225 vec![
5226 fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5227 route: expected_route,
5228 effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5229 metric: 0,
5230 },
5231 table_id: MAIN_FIDL_TABLE_ID,
5232 })
5233 .try_into()
5234 .unwrap(),
5235 fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5236 route: expected_route,
5237 effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5238 metric: 0,
5239 },
5240 table_id: OTHER_FIDL_TABLE_ID,
5241 })
5242 .try_into()
5243 .unwrap(),
5244 ],
5245 );
5246 };
5247 let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5248 }
5249
5250 {
5251 let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5252
5253 assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
5255 &expected_route,
5256 [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5257 ));
5258
5259 assert_matches!(route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX), None);
5261 }
5262 assert_matches!(del_request_waiter.try_recv(), Ok(Some(Ok(()))));
5263
5264 let route_table_request = route_table_stream.next().await;
5267 assert!(route_table_request.is_none());
5268 };
5269 join_handle.await;
5270 }
5271
5272 #[ip_test(I, test = false)]
5273 #[fuchsia::test]
5274 async fn process_stashed_routes<
5275 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
5276 >() {
5277 let (subnet, next_hop) =
5278 I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
5279 let table_id = OTHER_FIDL_TABLE_ID;
5280 let installed_route =
5281 create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
5282 let to_be_removed_route =
5283 create_installed_route(subnet, Some(next_hop), DEV2.into(), METRIC2, table_id);
5284
5285 let (route_table_proxy, _route_table_server_end) =
5286 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
5287 let (unmanaged_route_set_proxy, _server_end) =
5288 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5289 let (route_table_provider, _server_end) =
5290 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
5291 let mut route_table = RouteTableMap::new(
5292 route_table_proxy.clone(),
5293 MAIN_FIDL_TABLE_ID,
5294 unmanaged_route_set_proxy,
5295 route_table_provider,
5296 );
5297 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
5298
5299 let mut worker = RoutesWorker {
5300 fidl_route_map: FidlRouteMap::<I>::default(),
5301 stashed_routes: HashMap::new(),
5302 };
5303
5304 assert_eq!(
5306 worker.handle_route_watcher_event(
5307 &mut route_table,
5308 &route_clients,
5309 fnet_routes_ext::Event::Added(installed_route.clone()),
5310 ),
5311 None
5312 );
5313 assert_eq!(
5314 worker.handle_route_watcher_event(
5315 &mut route_table,
5316 &route_clients,
5317 fnet_routes_ext::Event::Added(to_be_removed_route.clone()),
5318 ),
5319 None
5320 );
5321
5322 assert!(worker.stashed_routes.contains_key(&table_id));
5324 assert!(worker.stashed_routes[&table_id].contains(&installed_route));
5325 assert!(worker.stashed_routes[&table_id].contains(&to_be_removed_route));
5326 assert_eq!(
5327 worker.fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX).count(),
5329 0
5330 );
5331
5332 assert_eq!(
5333 worker.handle_route_watcher_event(
5334 &mut route_table,
5335 &route_clients,
5336 fnet_routes_ext::Event::Removed(to_be_removed_route.clone()),
5337 ),
5338 None
5339 );
5340 assert!(!worker.stashed_routes[&table_id].contains(&to_be_removed_route));
5341
5342 let netlink_table_id = NetlinkRouteTableIndex::new(123);
5344 let (route_set_proxy, _) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5345 let table = RouteTable::Unmanaged(UnmanagedTable {
5346 route_table_proxy: route_table_proxy.clone(),
5347 route_set_proxy,
5348 fidl_table_id: table_id,
5349 rule_set_authenticated: false,
5350 });
5351 route_table.insert(netlink_table_id, table);
5352
5353 worker.process_stashed_routes(&mut route_table, &route_clients, netlink_table_id);
5355
5356 assert!(!worker.stashed_routes.contains_key(&table_id));
5358 assert_eq!(worker.fidl_route_map.iter_messages(&route_table, netlink_table_id).count(), 1);
5361 }
5362
5363 #[ip_test(I, test = false)]
5364 #[fuchsia::test]
5365 async fn remove_non_empty_table<
5366 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
5367 >() {
5368 let (subnet, next_hop) =
5369 I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
5370 let table_id = OTHER_FIDL_TABLE_ID;
5371 let installed_route =
5372 create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
5373
5374 let (route_table_proxy, _route_table_server_end) =
5375 fidl::endpoints::create_proxy::<I::RouteTableMarker>();
5376 let (unmanaged_route_set_proxy, _server_end) =
5377 fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5378 let (route_table_provider, _server_end) =
5379 fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
5380 let mut route_table = RouteTableMap::new(
5381 route_table_proxy.clone(),
5382 MAIN_FIDL_TABLE_ID,
5383 unmanaged_route_set_proxy,
5384 route_table_provider,
5385 );
5386 let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
5387
5388 let mut worker = RoutesWorker {
5389 fidl_route_map: FidlRouteMap::<I>::default(),
5390 stashed_routes: HashMap::new(),
5391 };
5392
5393 let netlink_table_id = NetlinkRouteTableIndex::new(123);
5394 let (route_set_proxy, _) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5395
5396 let table = RouteTable::Unmanaged(UnmanagedTable {
5398 route_table_proxy: route_table_proxy.clone(),
5399 route_set_proxy,
5400 fidl_table_id: table_id,
5401 rule_set_authenticated: false,
5402 });
5403
5404 route_table.insert(netlink_table_id, table);
5405 assert_eq!(
5406 worker.handle_route_watcher_event(
5407 &mut route_table,
5408 &route_clients,
5409 fnet_routes_ext::Event::Added(installed_route.clone()),
5410 ),
5411 None
5412 );
5413
5414 assert_matches!(route_table.remove_table_by_fidl_id(table_id), Some(Ok(_)));
5416 assert_eq!(
5419 worker.handle_route_watcher_event(
5420 &mut route_table,
5421 &route_clients,
5422 fnet_routes_ext::Event::Removed(installed_route.clone()),
5423 ),
5424 None
5425 );
5426 }
5427}