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