1use heapless::Vec;
2
3use crate::config::IFACE_MAX_ROUTE_COUNT;
4use crate::time::Instant;
5use crate::wire::{IpAddress, IpCidr};
6#[cfg(feature = "proto-ipv4")]
7use crate::wire::{Ipv4Address, Ipv4Cidr};
8#[cfg(feature = "proto-ipv6")]
9use crate::wire::{Ipv6Address, Ipv6Cidr};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub struct RouteTableFull;
14
15impl core::fmt::Display for RouteTableFull {
16 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
17 write!(f, "Route table full")
18 }
19}
20
21impl core::error::Error for RouteTableFull {}
22
23#[derive(Debug, Clone, Copy)]
25#[cfg_attr(feature = "defmt", derive(defmt::Format))]
26pub struct Route {
27 pub cidr: IpCidr,
28 pub via_router: IpAddress,
29 pub preferred_until: Option<Instant>,
31 pub expires_at: Option<Instant>,
33}
34
35#[cfg(feature = "proto-ipv4")]
36const IPV4_DEFAULT: IpCidr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(0, 0, 0, 0), 0));
37#[cfg(feature = "proto-ipv6")]
38const IPV6_DEFAULT: IpCidr =
39 IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0), 0));
40
41impl Route {
42 #[cfg(feature = "proto-ipv4")]
44 pub fn new_ipv4_gateway(gateway: Ipv4Address) -> Route {
45 Route {
46 cidr: IPV4_DEFAULT,
47 via_router: gateway.into(),
48 preferred_until: None,
49 expires_at: None,
50 }
51 }
52
53 #[cfg(feature = "proto-ipv6")]
55 pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route {
56 Route {
57 cidr: IPV6_DEFAULT,
58 via_router: gateway.into(),
59 preferred_until: None,
60 expires_at: None,
61 }
62 }
63
64 #[cfg(feature = "proto-ipv6")]
66 pub fn is_ipv6_gateway(&self) -> bool {
67 self.cidr == IPV6_DEFAULT
68 }
69
70 #[cfg(feature = "proto-ipv4")]
72 pub fn is_ipv4_gateway(&self) -> bool {
73 self.cidr == IPV4_DEFAULT
74 }
75}
76
77#[derive(Debug)]
79pub struct Routes {
80 storage: Vec<Route, IFACE_MAX_ROUTE_COUNT>,
81}
82
83impl Routes {
84 pub fn new() -> Self {
86 Self {
87 storage: Vec::new(),
88 }
89 }
90
91 pub fn update<F: FnOnce(&mut Vec<Route, IFACE_MAX_ROUTE_COUNT>)>(&mut self, f: F) {
93 f(&mut self.storage);
94 }
95
96 #[cfg(feature = "proto-ipv4")]
100 pub fn add_default_ipv4_route(
101 &mut self,
102 gateway: Ipv4Address,
103 ) -> Result<Option<Route>, RouteTableFull> {
104 let old = self.remove_default_ipv4_route();
105 self.storage
106 .push(Route::new_ipv4_gateway(gateway))
107 .map_err(|_| RouteTableFull)?;
108 Ok(old)
109 }
110
111 #[cfg(feature = "proto-ipv6")]
115 pub fn add_default_ipv6_route(
116 &mut self,
117 gateway: Ipv6Address,
118 ) -> Result<Option<Route>, RouteTableFull> {
119 let old = self.remove_default_ipv6_route();
120 self.storage
121 .push(Route::new_ipv6_gateway(gateway))
122 .map_err(|_| RouteTableFull)?;
123 Ok(old)
124 }
125
126 #[cfg(feature = "proto-ipv4")]
128 pub fn get_default_ipv4_route(&self) -> Option<Route> {
129 self.storage.iter().find(|r| r.is_ipv4_gateway()).copied()
130 }
131
132 #[cfg(feature = "proto-ipv6")]
134 pub fn get_default_ipv6_route(&self) -> Option<Route> {
135 self.storage.iter().find(|r| r.is_ipv6_gateway()).copied()
136 }
137
138 #[cfg(feature = "proto-ipv4")]
142 pub fn remove_default_ipv4_route(&mut self) -> Option<Route> {
143 if let Some((i, _)) = self
144 .storage
145 .iter()
146 .enumerate()
147 .find(|(_, r)| r.is_ipv4_gateway())
148 {
149 Some(self.storage.remove(i))
150 } else {
151 None
152 }
153 }
154
155 #[cfg(feature = "proto-ipv6")]
159 pub fn remove_default_ipv6_route(&mut self) -> Option<Route> {
160 if let Some((i, _)) = self
161 .storage
162 .iter()
163 .enumerate()
164 .find(|(_, r)| r.is_ipv6_gateway())
165 {
166 Some(self.storage.remove(i))
167 } else {
168 None
169 }
170 }
171
172 pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) -> Option<IpAddress> {
173 assert!(addr.is_unicast());
174
175 self.storage
176 .iter()
177 .filter(|route| {
179 if let Some(expires_at) = route.expires_at
180 && timestamp > expires_at
181 {
182 return false;
183 }
184 route.cidr.contains_addr(addr)
185 })
186 .max_by_key(|route| route.cidr.prefix_len())
188 .map(|route| route.via_router)
189 }
190}
191
192#[cfg(test)]
193mod test {
194 use super::*;
195 #[cfg(feature = "proto-ipv6")]
196 mod mock {
197 use super::super::*;
198 pub const ADDR_1A: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 1);
199 pub const ADDR_1B: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 13);
200 pub const ADDR_1C: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 42);
201 pub fn cidr_1() -> Ipv6Cidr {
202 Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 0), 64)
203 }
204
205 pub const ADDR_2A: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 1);
206 pub const ADDR_2B: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 21);
207 pub fn cidr_2() -> Ipv6Cidr {
208 Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 0), 64)
209 }
210 }
211
212 #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
213 mod mock {
214 use super::super::*;
215 pub const ADDR_1A: Ipv4Address = Ipv4Address::new(192, 0, 2, 1);
216 pub const ADDR_1B: Ipv4Address = Ipv4Address::new(192, 0, 2, 13);
217 pub const ADDR_1C: Ipv4Address = Ipv4Address::new(192, 0, 2, 42);
218 pub fn cidr_1() -> Ipv4Cidr {
219 Ipv4Cidr::new(Ipv4Address::new(192, 0, 2, 0), 24)
220 }
221
222 pub const ADDR_2A: Ipv4Address = Ipv4Address::new(198, 51, 100, 1);
223 pub const ADDR_2B: Ipv4Address = Ipv4Address::new(198, 51, 100, 21);
224 pub fn cidr_2() -> Ipv4Cidr {
225 Ipv4Cidr::new(Ipv4Address::new(198, 51, 100, 0), 24)
226 }
227 }
228
229 use self::mock::*;
230
231 #[test]
232 fn test_fill() {
233 let mut routes = Routes::new();
234
235 assert_eq!(
236 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
237 None
238 );
239 assert_eq!(
240 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
241 None
242 );
243 assert_eq!(
244 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
245 None
246 );
247 assert_eq!(
248 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
249 None
250 );
251 assert_eq!(
252 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
253 None
254 );
255
256 let route = Route {
257 cidr: cidr_1().into(),
258 via_router: ADDR_1A.into(),
259 preferred_until: None,
260 expires_at: None,
261 };
262 routes.update(|storage| {
263 storage.push(route).unwrap();
264 });
265
266 assert_eq!(
267 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
268 Some(ADDR_1A.into())
269 );
270 assert_eq!(
271 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
272 Some(ADDR_1A.into())
273 );
274 assert_eq!(
275 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
276 Some(ADDR_1A.into())
277 );
278 assert_eq!(
279 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
280 None
281 );
282 assert_eq!(
283 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
284 None
285 );
286
287 let route2 = Route {
288 cidr: cidr_2().into(),
289 via_router: ADDR_2A.into(),
290 preferred_until: Some(Instant::from_millis(10)),
291 expires_at: Some(Instant::from_millis(10)),
292 };
293 routes.update(|storage| {
294 storage.push(route2).unwrap();
295 });
296
297 assert_eq!(
298 routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
299 Some(ADDR_1A.into())
300 );
301 assert_eq!(
302 routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
303 Some(ADDR_1A.into())
304 );
305 assert_eq!(
306 routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
307 Some(ADDR_1A.into())
308 );
309 assert_eq!(
310 routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
311 Some(ADDR_2A.into())
312 );
313 assert_eq!(
314 routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
315 Some(ADDR_2A.into())
316 );
317
318 assert_eq!(
319 routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)),
320 Some(ADDR_1A.into())
321 );
322 assert_eq!(
323 routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)),
324 Some(ADDR_1A.into())
325 );
326 assert_eq!(
327 routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)),
328 Some(ADDR_1A.into())
329 );
330 assert_eq!(
331 routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)),
332 Some(ADDR_2A.into())
333 );
334 assert_eq!(
335 routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)),
336 Some(ADDR_2A.into())
337 );
338 }
339}