1use fidl_fuchsia_net_ext::IntoExt;
6use log::error;
7use net_types::ip::{GenericOverIp, Ip, IpAddress, Ipv4, Ipv6};
8use std::collections::HashSet;
9use {fidl_fuchsia_net as fnet, fidl_fuchsia_net_routes_ext as fnet_routes_ext};
10
11#[derive(Debug, Default)]
14pub struct RouteTable {
15    v4_routes: HashSet<fnet_routes_ext::InstalledRoute<Ipv4>>,
16    v6_routes: HashSet<fnet_routes_ext::InstalledRoute<Ipv6>>,
17}
18
19#[derive(Debug, PartialEq)]
22pub(crate) struct Route {
23    pub(crate) destination: fnet::Subnet,
24    pub(crate) outbound_interface: u64,
25    pub(crate) next_hop: Option<fnet::IpAddress>,
26}
27
28pub(crate) struct RouteActionNotForwardingError {}
29
30impl<I: Ip> TryFrom<fnet_routes_ext::InstalledRoute<I>> for Route {
33    type Error = RouteActionNotForwardingError;
34    fn try_from(
35        fnet_routes_ext::InstalledRoute {
36            route: fnet_routes_ext::Route { destination, action, properties: _ },
37            effective_properties: _,
38            table_id: _,
41        }: fnet_routes_ext::InstalledRoute<I>,
42    ) -> Result<Self, Self::Error> {
43        match action {
44            fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget {
45                outbound_interface,
46                next_hop,
47            }) => Ok(Route {
48                destination: destination.into_ext(),
49                outbound_interface,
50                next_hop: next_hop.map(|next_hop| (*next_hop).to_ip_addr().into_ext()),
51            }),
52            fnet_routes_ext::RouteAction::Unknown => Err(RouteActionNotForwardingError {}),
53        }
54    }
55}
56
57impl Route {
60    fn optionally_from<I: Ip>(route: fnet_routes_ext::InstalledRoute<I>) -> Option<Route> {
61        match route.try_into() {
62            Ok(route) => Some(route),
63            Err(RouteActionNotForwardingError {}) => {
64                error!("Unexpected route in routing table: {:?}", route);
65                None
66            }
67        }
68    }
69}
70
71impl RouteTable {
72    pub fn new_with_existing_routes(
74        v4_routes: HashSet<fnet_routes_ext::InstalledRoute<Ipv4>>,
75        v6_routes: HashSet<fnet_routes_ext::InstalledRoute<Ipv6>>,
76    ) -> RouteTable {
77        return RouteTable { v4_routes, v6_routes };
78    }
79
80    fn get_ip_specific_table_mut<I: Ip>(
83        &mut self,
84    ) -> &mut HashSet<fnet_routes_ext::InstalledRoute<I>> {
85        #[derive(GenericOverIp)]
86        #[generic_over_ip(I, Ip)]
87        struct TableHolder<'a, I: Ip>(&'a mut HashSet<fnet_routes_ext::InstalledRoute<I>>);
88        let TableHolder(ip_specific_table) = I::map_ip_out(
89            self,
90            |route_table| TableHolder(&mut route_table.v4_routes),
91            |route_table| TableHolder(&mut route_table.v6_routes),
92        );
93        ip_specific_table
94    }
95
96    pub fn add_route<I: Ip>(&mut self, route: fnet_routes_ext::InstalledRoute<I>) -> bool {
99        self.get_ip_specific_table_mut::<I>().insert(route)
100    }
101
102    pub fn remove_route<I: Ip>(&mut self, route: &fnet_routes_ext::InstalledRoute<I>) -> bool {
105        self.get_ip_specific_table_mut::<I>().remove(route)
106    }
107
108    pub(crate) fn device_routes(&self, device: u64) -> impl Iterator<Item = Route> + '_ {
110        let RouteTable { v4_routes, v6_routes } = self;
111        v4_routes
112            .iter()
113            .filter_map(|route| Route::optionally_from(*route))
114            .chain(v6_routes.iter().filter_map(|route| Route::optionally_from(*route)))
115            .filter(move |Route { destination: _, outbound_interface, next_hop: _ }| {
116                *outbound_interface == device
117            })
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::testutil;
125    use net_declare::fidl_subnet;
126    use test_case::test_case;
127
128    #[test_case([], 1, []; "empty route table")]
129    #[test_case(
130        [Route{
131            destination: fidl_subnet!("192.0.2.0/24"),
132            outbound_interface: 2,
133            next_hop: None
134        },
135        Route{
136            destination: fidl_subnet!("2001:db8::/32"),
137            outbound_interface: 2,
138            next_hop: None
139        }],
140        1,
141        [];
142        "0 device routes")]
143    #[test_case(
144        [Route{
145            destination: fidl_subnet!("192.0.2.0/24"),
146            outbound_interface: 1,
147            next_hop: None
148        },
149        Route{
150            destination: fidl_subnet!("2001:db8::/32"),
151            outbound_interface: 2,
152            next_hop: None
153        }],
154        1,
155        [Route{
156            destination: fidl_subnet!("192.0.2.0/24"),
157            outbound_interface: 1,
158            next_hop: None
159        }];
160        "IPv4 device route")]
161    #[test_case(
162        [Route{
163            destination: fidl_subnet!("192.0.2.0/24"),
164            outbound_interface: 2,
165            next_hop: None
166        },
167        Route{
168            destination: fidl_subnet!("2001:db8::/32"),
169            outbound_interface: 1,
170            next_hop: None
171        }],
172        1,
173        [Route{
174            destination: fidl_subnet!("2001:db8::/32"),
175            outbound_interface: 1,
176            next_hop: None
177        }];
178        "IPv6 device route")]
179    #[test_case(
180        [Route{
181            destination: fidl_subnet!("192.0.2.0/24"),
182            outbound_interface: 1,
183            next_hop: None
184        },
185        Route{
186            destination: fidl_subnet!("2001:db8::/32"),
187            outbound_interface: 1,
188            next_hop: None
189        }],
190        1,
191        [Route{
192            destination: fidl_subnet!("192.0.2.0/24"),
193            outbound_interface: 1,
194            next_hop: None
195        },
196        Route{
197            destination: fidl_subnet!("2001:db8::/32"),
198            outbound_interface: 1,
199            next_hop: None
200        }];
201        "IPv4 & IPv6 device routes")]
202    fn device_routes(
203        all_routes: impl IntoIterator<Item = Route>,
204        device_id: u64,
205        expected_routes: impl IntoIterator<Item = Route>,
206    ) {
207        assert_eq!(
208            testutil::build_route_table_from_flattened_routes(all_routes)
209                .device_routes(device_id)
210                .collect::<Vec<_>>(),
211            expected_routes.into_iter().collect::<Vec<_>>()
212        );
213    }
214}