1use crate::{Destination, Entity, Protocol, RoutingFlag};
2use cidr::AnyIpCidr;
3use mac_address::MacAddress;
4use std::{
5 collections::HashSet,
6 net::{IpAddr, Ipv4Addr, Ipv6Addr},
7 time::Duration,
8};
9
10#[derive(Debug, Clone)]
12pub struct RouteEntry {
13 pub proto: Protocol,
15
16 pub dest: Destination,
18
19 pub gateway: Destination,
21
22 pub flags: HashSet<RoutingFlag>,
24
25 pub net_if: String,
27
28 pub expires: Option<Duration>,
30}
31
32impl std::fmt::Display for RouteEntry {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 #[allow(unused_variables)]
35 let RouteEntry {
36 proto,
37 dest,
38 gateway,
39 flags,
40 net_if,
41 expires,
42 } = self;
43 write!(f, "{proto:?}({dest} -> {gateway} if={net_if}")
44 }
45}
46
47#[derive(Debug, thiserror::Error)]
48pub enum Error {
49 #[error("parsing destination CIDR {value:?}: {err}")]
50 ParseDestination {
51 value: String,
52 err: cidr::errors::NetworkParseError,
53 },
54
55 #[error("parsing MAC addr {dest:?}: {err}")]
56 ParseMacAddr {
57 dest: String,
58 err: mac_address::MacParseError,
59 },
60
61 #[error("unparseable byte in IPv4 address {addr:?}: {err}")]
62 ParseIPv4AddrBadInt {
63 addr: String,
64 err: std::num::ParseIntError,
65 },
66
67 #[error("invalid number of IPv4 address components ({n_comps}) in {addr:?}")]
68 ParseIPv4AddrNComps { n_comps: usize, addr: String },
69
70 #[error("invalid expiration {expiration:?}: {err}")]
71 ParseExpiration {
72 expiration: String,
73 err: std::num::ParseIntError,
74 },
75
76 #[error("missing destination")]
77 MissingDestination,
78
79 #[error("missing gateway")]
80 MissingGateway,
81
82 #[error("missing network interface")]
83 MissingInterface,
84}
85
86impl RouteEntry {
87 pub(crate) fn parse(proto: Protocol, line: &str, headers: &[&str]) -> Result<Self, Error> {
90 let fields: Vec<String> = line.split_ascii_whitespace().map(str::to_string).collect();
91 let mut flags = HashSet::new();
92 let mut dest = None;
93 let mut gateway = None;
94 let mut net_if: Option<String> = None;
95 let mut expires = None;
96
97 for (header, field) in headers.iter().zip(fields) {
99 match *header {
100 "Destination" => dest = Some(parse_destination(&field)?),
101 "Gateway" => gateway = Some(parse_destination(&field)?),
102 "Flags" => flags = parse_flags(&field),
103 "Netif" => net_if = Some(field),
104 "Expire" => expires = parse_expire(&field)?,
105 _ => (),
106 }
107 }
108
109 let route = RouteEntry {
110 proto,
111 dest: dest.ok_or(Error::MissingDestination)?,
112 gateway: gateway.ok_or(Error::MissingGateway)?,
113 flags,
114 net_if: net_if.ok_or(Error::MissingInterface)?,
115 expires,
116 };
117 Ok(route)
118 }
119
120 pub(crate) fn contains(&self, addr: IpAddr) -> bool {
122 match self.dest.entity {
123 Entity::Cidr(cidr) => cidr.contains(&addr),
124 Entity::Default => match self.gateway.entity {
125 Entity::Cidr(_) => match addr {
126 IpAddr::V4(_) => matches!(self.proto, Protocol::V4),
127 IpAddr::V6(_) => matches!(self.proto, Protocol::V6),
129 },
130 Entity::Link(_) | Entity::Mac(_) | Entity::Default => false,
132 },
133 _ => false,
134 }
135 }
136
137 pub(crate) fn most_precise<'a>(&'a self, other: &'a Self) -> &'a Self {
141 match self.dest.entity {
142 Entity::Mac(_) => self,
145 Entity::Link(_) => match other.dest.entity {
146 Entity::Mac(_) => other,
148 _ => self,
150 },
151 Entity::Cidr(cidr) => match other.dest.entity {
152 Entity::Mac(_) | Entity::Link(_) => other,
153 Entity::Cidr(other_cidr) => {
154 let Some(cidr_nl) = cidr.network_length() else {
155 return other;
157 };
158
159 let Some(other_nl) = other_cidr.network_length() else {
160 return self;
162 };
163
164 if cidr_nl >= other_nl {
166 self
167 } else {
168 other
169 }
170 }
171 Entity::Default => self,
172 },
173 Entity::Default => match other.dest.entity {
174 Entity::Default => self,
176 _ => other,
177 },
178 }
179 }
180}
181
182fn parse_destination(dest: &str) -> Result<Destination, Error> {
183 if dest.starts_with("link") {
184 return Ok(Destination {
185 entity: Entity::Link(dest.to_owned()),
186 zone: None,
187 });
188 }
189 Ok(if let Some((addr, zone_etc)) = dest.split_once('%') {
190 let addr: AnyIpCidr = addr.parse().map_err(|err| Error::ParseDestination {
193 value: addr.into(),
194 err,
195 })?;
196 let mut zone_etc = zone_etc.split('/');
197 let zone = zone_etc.next().map(ToOwned::to_owned);
198
199 if let Some(bits) = zone_etc.next() {
200 let s = format!("{addr}{bits}");
202 Destination {
203 entity: parse_simple_destination(&s)?,
204 zone,
205 }
206 } else {
207 Destination {
208 entity: Entity::Cidr(addr),
209 zone,
210 }
211 }
212 } else {
213 Destination {
214 entity: parse_simple_destination(dest)?,
215 zone: None,
216 }
217 })
218}
219
220fn parse_simple_destination(dest: &str) -> Result<Entity, Error> {
221 Ok(match dest {
222 "default" => Entity::Default,
223
224 cidr if cidr.contains('/') => {
225 Entity::Cidr(cidr.parse().map_err(|err| Error::ParseDestination {
226 value: cidr.into(),
227 err,
228 })?)
229 }
230 addr if addr.contains('.') => {
232 if let Ok(ipv4addr) = parse_ipv4dest(addr) {
233 Entity::Cidr(AnyIpCidr::new_host(IpAddr::V4(ipv4addr)))
234 } else {
235 Entity::Mac(
237 addr.replace('.', ":")
238 .parse::<MacAddress>()
239 .map_err(|err| Error::ParseMacAddr {
240 dest: addr.into(),
241 err,
242 })?,
243 )
244 }
245 }
246 addr if addr.contains(':') => {
248 if let Ok(v6addr) = addr.parse::<Ipv6Addr>() {
249 Entity::Cidr(AnyIpCidr::new_host(IpAddr::V6(v6addr)))
250 } else {
251 Entity::Mac(
253 addr.parse::<MacAddress>()
254 .map_err(|err| Error::ParseMacAddr {
255 dest: addr.into(),
256 err,
257 })?,
258 )
259 }
260 }
261 num => Entity::Cidr(AnyIpCidr::new_host(IpAddr::V4(parse_ipv4dest(num)?))),
263 })
264}
265
266fn parse_flags(flags_s: &str) -> HashSet<RoutingFlag> {
267 flags_s.chars().map(RoutingFlag::from).collect()
268}
269
270fn parse_expire(s: &str) -> Result<Option<Duration>, Error> {
271 match s {
272 "!" => Ok(None),
273 n => Ok(Some(Duration::from_secs(n.parse().map_err(|err| {
274 Error::ParseExpiration {
275 expiration: s.into(),
276 err,
277 }
278 })?))),
279 }
280}
281
282fn parse_ipv4dest(dest: &str) -> Result<Ipv4Addr, Error> {
283 dest.parse::<Ipv4Addr>().or_else(|_| {
284 let parts: Vec<u8> = dest
285 .split('.')
286 .map(str::parse)
287 .collect::<std::result::Result<Vec<u8>, std::num::ParseIntError>>()
288 .map_err(|err| Error::ParseIPv4AddrBadInt {
289 addr: dest.into(),
290 err,
291 })?;
292 match parts.len() {
294 3 => Ok(Ipv4Addr::new(parts[0], parts[1], 0, parts[2])),
295 2 => Ok(Ipv4Addr::new(parts[0], 0, 0, parts[1])),
296 1 => Ok(Ipv4Addr::new(0, 0, 0, parts[0])),
297 len => Err(Error::ParseIPv4AddrNComps {
298 n_comps: len,
299 addr: dest.into(),
300 }),
301 }
302 })
303}