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