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}