Skip to main content

ftth_rtnl/
route.rs

1#![allow(unreachable_patterns)]
2
3use std::io::{self, ErrorKind};
4use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
5
6use ftth_common::channel::{AsyncWorldClient, AsyncWorldServer};
7use futures::TryStreamExt;
8use tracing::warn;
9use netlink_packet_route::AddressFamily;
10use netlink_packet_route::route::{
11    RouteAddress, RouteAttribute, RouteMessage, RouteNextHop, RouteNextHopFlags, RouteType,
12    RouteVia,
13};
14use rtnetlink::RouteMessageBuilder;
15
16pub(crate) type Client = AsyncWorldClient<RtnlRouteRequest, RtnlRouteResponse>;
17pub(crate) type Server = AsyncWorldServer<RtnlRouteRequest, RtnlRouteResponse>;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Ipv4Route {
21    pub if_id: Option<u32>,
22    pub gateway: Option<IpAddr>,
23    pub source: Option<Ipv4Addr>,
24    pub metric: Option<u32>,
25    pub table: Option<u32>,
26    pub route: crate::Ipv4Net,
27    pub nexthops: Vec<RouteNextHopInfo>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Ipv6Route {
32    pub if_id: Option<u32>,
33    pub gateway: Option<IpAddr>,
34    pub source: Option<Ipv6Addr>,
35    pub metric: Option<u32>,
36    pub table: Option<u32>,
37    pub route: crate::Ipv6Net,
38    pub nexthops: Vec<RouteNextHopInfo>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct RouteNextHopInfo {
43    pub if_id: Option<u32>,
44    pub gateway: Option<IpAddr>,
45    pub weight: u32,
46    pub flags: RouteNextHopFlags,
47}
48
49#[derive(Debug, Clone, PartialEq)]
50#[non_exhaustive]
51pub enum RtnlRouteRequest {
52    Ipv4RouteList,
53    Ipv6RouteList,
54    Ipv4RouteAdd(Ipv4Route),
55    Ipv4RouteReplace(Ipv4Route),
56    Ipv6RouteAdd(Ipv6Route),
57    Ipv6RouteReplace(Ipv6Route),
58    Ipv4RouteDel(Ipv4Route),
59    Ipv6RouteDel(Ipv6Route),
60    Ipv4RouteGet(Ipv4Addr),
61    Ipv6RouteGet(Ipv6Addr),
62    Ipv4RouteGetByPrefix(crate::Ipv4Net),
63    Ipv6RouteGetByPrefix(crate::Ipv6Net),
64}
65
66#[derive(Debug, Clone, PartialEq)]
67#[non_exhaustive]
68pub enum RtnlRouteResponse {
69    Success,
70    Failed,
71    NotImplemented,
72    NotFound,
73    Ipv4RouteList(Vec<Ipv4Route>),
74    Ipv6RouteList(Vec<Ipv6Route>),
75    Ipv4Route(Ipv4Route),
76    Ipv6Route(Ipv6Route),
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub struct RtnlRouteClient {
81    client: Client,
82}
83
84impl RtnlRouteClient {
85    pub(crate) fn new(client: Client) -> Self {
86        Self { client }
87    }
88
89    pub fn ipv4_route_add(&self, route: Ipv4Route) -> io::Result<()> {
90        let res = self
91            .client
92            .send_request(RtnlRouteRequest::Ipv4RouteAdd(route))?;
93        handle_route_status("IPv4 route add", res)
94    }
95
96    pub fn ipv4_route_replace(&self, route: Ipv4Route) -> io::Result<()> {
97        let res = self
98            .client
99            .send_request(RtnlRouteRequest::Ipv4RouteReplace(route))?;
100        handle_route_status("IPv4 route replace", res)
101    }
102
103    pub fn ipv4_route_del(&self, route: Ipv4Route) -> io::Result<()> {
104        let res = self
105            .client
106            .send_request(RtnlRouteRequest::Ipv4RouteDel(route))?;
107        handle_route_status("IPv4 route delete", res)
108    }
109
110    pub fn ipv4_route_list(&self) -> io::Result<Vec<Ipv4Route>> {
111        match self.client.send_request(RtnlRouteRequest::Ipv4RouteList)? {
112            RtnlRouteResponse::Ipv4RouteList(routes) => Ok(routes),
113            other => Err(io::Error::other(format!(
114                "Unexpected response for IPv4 route list: {:?}",
115                other
116            ))),
117        }
118    }
119
120    pub fn ipv4_route_get(&self, destination: Ipv4Addr) -> io::Result<Ipv4Route> {
121        match self
122            .client
123            .send_request(RtnlRouteRequest::Ipv4RouteGet(destination))?
124        {
125            RtnlRouteResponse::Ipv4Route(route) => Ok(route),
126            RtnlRouteResponse::NotFound => {
127                Err(io::Error::new(ErrorKind::NotFound, "Route not found"))
128            }
129            other => Err(io::Error::other(format!(
130                "Unexpected response for IPv4 route get: {:?}",
131                other
132            ))),
133        }
134    }
135
136    pub fn ipv4_route_get_by_prefix(&self, prefix: crate::Ipv4Net) -> io::Result<Ipv4Route> {
137        match self
138            .client
139            .send_request(RtnlRouteRequest::Ipv4RouteGetByPrefix(prefix))?
140        {
141            RtnlRouteResponse::Ipv4Route(route) => Ok(route),
142            RtnlRouteResponse::NotFound => {
143                Err(io::Error::new(ErrorKind::NotFound, "Route not found"))
144            }
145            other => Err(io::Error::other(format!(
146                "Unexpected response for IPv4 route get by prefix: {:?}",
147                other
148            ))),
149        }
150    }
151
152    pub fn ipv6_route_add(&self, route: Ipv6Route) -> io::Result<()> {
153        let res = self
154            .client
155            .send_request(RtnlRouteRequest::Ipv6RouteAdd(route))?;
156        handle_route_status("IPv6 route add", res)
157    }
158
159    pub fn ipv6_route_replace(&self, route: Ipv6Route) -> io::Result<()> {
160        let res = self
161            .client
162            .send_request(RtnlRouteRequest::Ipv6RouteReplace(route))?;
163        handle_route_status("IPv6 route replace", res)
164    }
165
166    pub fn ipv6_route_del(&self, route: Ipv6Route) -> io::Result<()> {
167        let res = self
168            .client
169            .send_request(RtnlRouteRequest::Ipv6RouteDel(route))?;
170        handle_route_status("IPv6 route delete", res)
171    }
172
173    pub fn ipv6_route_list(&self) -> io::Result<Vec<Ipv6Route>> {
174        match self.client.send_request(RtnlRouteRequest::Ipv6RouteList)? {
175            RtnlRouteResponse::Ipv6RouteList(routes) => Ok(routes),
176            other => Err(io::Error::other(format!(
177                "Unexpected response for IPv6 route list: {:?}",
178                other
179            ))),
180        }
181    }
182
183    pub fn ipv6_route_get(&self, destination: Ipv6Addr) -> io::Result<Ipv6Route> {
184        match self
185            .client
186            .send_request(RtnlRouteRequest::Ipv6RouteGet(destination))?
187        {
188            RtnlRouteResponse::Ipv6Route(route) => Ok(route),
189            RtnlRouteResponse::NotFound => {
190                Err(io::Error::new(ErrorKind::NotFound, "Route not found"))
191            }
192            other => Err(io::Error::other(format!(
193                "Unexpected response for IPv6 route get: {:?}",
194                other
195            ))),
196        }
197    }
198
199    pub fn ipv6_route_get_by_prefix(&self, prefix: crate::Ipv6Net) -> io::Result<Ipv6Route> {
200        match self
201            .client
202            .send_request(RtnlRouteRequest::Ipv6RouteGetByPrefix(prefix))?
203        {
204            RtnlRouteResponse::Ipv6Route(route) => Ok(route),
205            RtnlRouteResponse::NotFound => {
206                Err(io::Error::new(ErrorKind::NotFound, "Route not found"))
207            }
208            other => Err(io::Error::other(format!(
209                "Unexpected response for IPv6 route get by prefix: {:?}",
210                other
211            ))),
212        }
213    }
214}
215
216pub(crate) async fn run_server(mut server: Server, handle: rtnetlink::RouteHandle) {
217    while let Some((req, respond)) = server.accept().await {
218        let response = match req {
219            RtnlRouteRequest::Ipv4RouteList => list_routes_v4(&handle).await,
220            RtnlRouteRequest::Ipv6RouteList => list_routes_v6(&handle).await,
221            RtnlRouteRequest::Ipv4RouteAdd(route) => add_route_v4(&handle, route, false).await,
222            RtnlRouteRequest::Ipv4RouteReplace(route) => add_route_v4(&handle, route, true).await,
223            RtnlRouteRequest::Ipv6RouteAdd(route) => add_route_v6(&handle, route, false).await,
224            RtnlRouteRequest::Ipv6RouteReplace(route) => add_route_v6(&handle, route, true).await,
225            RtnlRouteRequest::Ipv4RouteDel(route) => delete_route_v4(&handle, route).await,
226            RtnlRouteRequest::Ipv6RouteDel(route) => delete_route_v6(&handle, route).await,
227            RtnlRouteRequest::Ipv4RouteGet(destination) => get_route_v4(&handle, destination).await,
228            RtnlRouteRequest::Ipv6RouteGet(destination) => get_route_v6(&handle, destination).await,
229            RtnlRouteRequest::Ipv4RouteGetByPrefix(prefix) => {
230                get_route_v4_by_prefix(&handle, prefix).await
231            }
232            RtnlRouteRequest::Ipv6RouteGetByPrefix(prefix) => {
233                get_route_v6_by_prefix(&handle, prefix).await
234            }
235        };
236        respond(response);
237    }
238}
239
240fn handle_route_status(op: &str, response: RtnlRouteResponse) -> io::Result<()> {
241    match response {
242        RtnlRouteResponse::Success => Ok(()),
243        RtnlRouteResponse::NotFound => Err(io::Error::new(
244            ErrorKind::NotFound,
245            format!("{}: route not found", op),
246        )),
247        RtnlRouteResponse::Failed => Err(io::Error::other(format!("{} failed", op))),
248        RtnlRouteResponse::NotImplemented => Err(io::Error::new(
249            ErrorKind::Unsupported,
250            format!("{} not implemented", op),
251        )),
252        other => Err(io::Error::other(format!(
253            "{} returned unexpected response: {:?}",
254            op, other
255        ))),
256    }
257}
258
259async fn list_routes_v4(handle: &rtnetlink::RouteHandle) -> RtnlRouteResponse {
260    let message = RouteMessageBuilder::<Ipv4Addr>::new().build();
261    let stream = handle.get(message).execute();
262    futures::pin_mut!(stream);
263    let mut routes = Vec::new();
264    loop {
265        match stream.try_next().await {
266            Ok(Some(msg)) => {
267                if let Some(route) = decode_ipv4_route(msg) {
268                    routes.push(route);
269                }
270            }
271            Ok(None) => break,
272            Err(err) => {
273                warn!("Failed to list IPv4 routes: {}", err);
274                return RtnlRouteResponse::Failed;
275            }
276        }
277    }
278    RtnlRouteResponse::Ipv4RouteList(routes)
279}
280
281async fn list_routes_v6(handle: &rtnetlink::RouteHandle) -> RtnlRouteResponse {
282    let message = RouteMessageBuilder::<Ipv6Addr>::new().build();
283    let stream = handle.get(message).execute();
284    futures::pin_mut!(stream);
285    let mut routes = Vec::new();
286    loop {
287        match stream.try_next().await {
288            Ok(Some(msg)) => {
289                if let Some(route) = decode_ipv6_route(msg) {
290                    routes.push(route);
291                }
292            }
293            Ok(None) => break,
294            Err(err) => {
295                warn!("Failed to list IPv6 routes: {}", err);
296                return RtnlRouteResponse::Failed;
297            }
298        }
299    }
300    RtnlRouteResponse::Ipv6RouteList(routes)
301}
302
303async fn add_route_v4(
304    handle: &rtnetlink::RouteHandle,
305    route: Ipv4Route,
306    replace: bool,
307) -> RtnlRouteResponse {
308    let message = build_ipv4_route_message(&route);
309    let request = handle.add(message);
310    let request = if replace { request.replace() } else { request };
311    map_route_result(
312        request.execute().await,
313        if replace {
314            "replace IPv4 route"
315        } else {
316            "add IPv4 route"
317        },
318    )
319}
320
321async fn add_route_v6(
322    handle: &rtnetlink::RouteHandle,
323    route: Ipv6Route,
324    replace: bool,
325) -> RtnlRouteResponse {
326    let message = build_ipv6_route_message(&route);
327    let request = handle.add(message);
328    let request = if replace { request.replace() } else { request };
329    map_route_result(
330        request.execute().await,
331        if replace {
332            "replace IPv6 route"
333        } else {
334            "add IPv6 route"
335        },
336    )
337}
338
339async fn delete_route_v4(handle: &rtnetlink::RouteHandle, route: Ipv4Route) -> RtnlRouteResponse {
340    let message = build_ipv4_route_message(&route);
341    map_route_result(handle.del(message).execute().await, "delete IPv4 route")
342}
343
344async fn delete_route_v6(handle: &rtnetlink::RouteHandle, route: Ipv6Route) -> RtnlRouteResponse {
345    let message = build_ipv6_route_message(&route);
346    map_route_result(handle.del(message).execute().await, "delete IPv6 route")
347}
348
349async fn get_route_v4(handle: &rtnetlink::RouteHandle, destination: Ipv4Addr) -> RtnlRouteResponse {
350    let target = destination;
351    let message = build_route_message_v4(Some(destination), 32);
352    lookup_route_v4(handle, message, move |route| route.route.contains(&target)).await
353}
354
355async fn get_route_v4_by_prefix(
356    handle: &rtnetlink::RouteHandle,
357    prefix: crate::Ipv4Net,
358) -> RtnlRouteResponse {
359    let target = prefix;
360    let message = build_route_message_v4(
361        if prefix.prefix_len() == 0 {
362            None
363        } else {
364            Some(prefix.addr())
365        },
366        prefix.prefix_len(),
367    );
368    lookup_route_v4(handle, message, move |route| route.route == target).await
369}
370
371async fn get_route_v6(handle: &rtnetlink::RouteHandle, destination: Ipv6Addr) -> RtnlRouteResponse {
372    let target = destination;
373    let message = build_route_message_v6(Some(destination), 128);
374    lookup_route_v6(handle, message, move |route| route.route.contains(&target)).await
375}
376
377async fn get_route_v6_by_prefix(
378    handle: &rtnetlink::RouteHandle,
379    prefix: crate::Ipv6Net,
380) -> RtnlRouteResponse {
381    let target = prefix;
382    let message = build_route_message_v6(
383        if prefix.prefix_len() == 0 {
384            None
385        } else {
386            Some(prefix.addr())
387        },
388        prefix.prefix_len(),
389    );
390    lookup_route_v6(handle, message, move |route| route.route == target).await
391}
392
393async fn lookup_route<F>(
394    handle: &rtnetlink::RouteHandle,
395    message: RouteMessage,
396    map: F,
397) -> RtnlRouteResponse
398where
399    F: Fn(RouteMessage) -> Option<RtnlRouteResponse>,
400{
401    let stream = handle.get(message).execute();
402    futures::pin_mut!(stream);
403    loop {
404        match stream.try_next().await {
405            Ok(Some(msg)) => {
406                if let Some(resp) = map(msg) {
407                    return resp;
408                }
409            }
410            Ok(None) => return RtnlRouteResponse::NotFound,
411            Err(err) => {
412                warn!("Failed to get route: {}", err);
413                return RtnlRouteResponse::Failed;
414            }
415        }
416    }
417}
418
419async fn lookup_route_v4<F>(
420    handle: &rtnetlink::RouteHandle,
421    message: RouteMessage,
422    predicate: F,
423) -> RtnlRouteResponse
424where
425    F: Fn(&Ipv4Route) -> bool,
426{
427    lookup_route(handle, message, |msg| {
428        if matches!(msg.header.kind, RouteType::Local | RouteType::Broadcast) {
429            return None;
430        }
431        decode_ipv4_route(msg).and_then(|route| {
432            if predicate(&route) {
433                Some(RtnlRouteResponse::Ipv4Route(route))
434            } else {
435                None
436            }
437        })
438    })
439    .await
440}
441
442async fn lookup_route_v6<F>(
443    handle: &rtnetlink::RouteHandle,
444    message: RouteMessage,
445    predicate: F,
446) -> RtnlRouteResponse
447where
448    F: Fn(&Ipv6Route) -> bool,
449{
450    lookup_route(handle, message, |msg| {
451        if matches!(msg.header.kind, RouteType::Local | RouteType::Broadcast) {
452            return None;
453        }
454        decode_ipv6_route(msg).and_then(|route| {
455            if predicate(&route) {
456                Some(RtnlRouteResponse::Ipv6Route(route))
457            } else {
458                None
459            }
460        })
461    })
462    .await
463}
464
465fn build_route_message_v4(destination: Option<Ipv4Addr>, prefix_len: u8) -> RouteMessage {
466    let mut message = RouteMessage::default();
467    message.header.address_family = AddressFamily::Inet;
468    message.header.destination_prefix_length = prefix_len;
469    if let Some(addr) = destination {
470        message
471            .attributes
472            .push(RouteAttribute::Destination(RouteAddress::Inet(addr)));
473    }
474    message
475}
476
477fn build_route_message_v6(destination: Option<Ipv6Addr>, prefix_len: u8) -> RouteMessage {
478    let mut message = RouteMessage::default();
479    message.header.address_family = AddressFamily::Inet6;
480    message.header.destination_prefix_length = prefix_len;
481    if let Some(addr) = destination {
482        message
483            .attributes
484            .push(RouteAttribute::Destination(RouteAddress::Inet6(addr)));
485    }
486    message
487}
488
489fn map_route_result(result: Result<(), rtnetlink::Error>, op: &str) -> RtnlRouteResponse {
490    match result {
491        Ok(()) => RtnlRouteResponse::Success,
492        Err(rtnetlink::Error::NetlinkError(err_msg)) => {
493            let io_err = err_msg.to_io();
494            match io_err.kind() {
495                ErrorKind::NotFound => RtnlRouteResponse::NotFound,
496                ErrorKind::AlreadyExists => {
497                    warn!("Route operation failed (already exists): {}", io_err);
498                    RtnlRouteResponse::Failed
499                }
500                _ => {
501                    warn!("Route operation '{}' failed: {}", op, io_err);
502                    RtnlRouteResponse::Failed
503                }
504            }
505        }
506        Err(err) => {
507            warn!("Route operation '{}' failed: {}", op, err);
508            RtnlRouteResponse::Failed
509        }
510    }
511}
512
513fn build_ipv4_route_message(route: &Ipv4Route) -> RouteMessage {
514    let mut builder = RouteMessageBuilder::<Ipv4Addr>::new()
515        .destination_prefix(route.route.addr(), route.route.prefix_len());
516
517    if let Some(if_id) = route.if_id.filter(|id| *id != 0) {
518        builder = builder.output_interface(if_id);
519    }
520
521    if let Some(gw) = route.gateway {
522        match gw {
523            IpAddr::V4(addr) => {
524                builder = builder.gateway(addr);
525            }
526            IpAddr::V6(addr) => {
527                builder
528                    .get_mut()
529                    .attributes
530                    .push(RouteAttribute::Via(RouteVia::Inet6(addr)));
531            }
532        }
533    }
534
535    if let Some(src) = route.source {
536        builder = builder.pref_source(src);
537    }
538
539    if let Some(metric) = route.metric {
540        builder = builder.priority(metric);
541    }
542
543    if let Some(table) = route.table {
544        builder = builder.table_id(table);
545    }
546
547    if !route.nexthops.is_empty() {
548        if let Some(multipath) = build_multipath_v4(&route.nexthops) {
549            builder = builder.multipath(multipath);
550        }
551    }
552
553    builder.build()
554}
555
556fn build_ipv6_route_message(route: &Ipv6Route) -> RouteMessage {
557    let mut builder = RouteMessageBuilder::<Ipv6Addr>::new()
558        .destination_prefix(route.route.addr(), route.route.prefix_len());
559
560    if let Some(if_id) = route.if_id.filter(|id| *id != 0) {
561        builder = builder.output_interface(if_id);
562    }
563
564    if let Some(gw) = route.gateway {
565        match gw {
566            IpAddr::V6(addr) => {
567                builder = builder.gateway(addr);
568            }
569            IpAddr::V4(addr) => {
570                builder
571                    .get_mut()
572                    .attributes
573                    .push(RouteAttribute::Via(RouteVia::Inet(addr)));
574            }
575        }
576    }
577
578    if let Some(src) = route.source {
579        builder = builder.pref_source(src);
580    }
581
582    if let Some(metric) = route.metric {
583        builder = builder.priority(metric);
584    }
585
586    if let Some(table) = route.table {
587        builder = builder.table_id(table);
588    }
589
590    if !route.nexthops.is_empty() {
591        if let Some(multipath) = build_multipath_v6(&route.nexthops) {
592            builder = builder.multipath(multipath);
593        }
594    }
595
596    builder.build()
597}
598
599fn decode_ipv4_route(message: RouteMessage) -> Option<Ipv4Route> {
600    if message.header.address_family != AddressFamily::Inet {
601        return None;
602    }
603
604    let header = message.header;
605    let mut destination = None;
606    let mut gateway: Option<IpAddr> = None;
607    let mut source = None;
608    let mut metric = None;
609    let mut table = table_from_header(header.table);
610    let mut oif = None;
611    let mut nexthops = Vec::new();
612
613    for attr in message.attributes {
614        match attr {
615            RouteAttribute::Destination(RouteAddress::Inet(addr)) => destination = Some(addr),
616            RouteAttribute::Gateway(RouteAddress::Inet(addr)) => gateway = Some(IpAddr::V4(addr)),
617            RouteAttribute::Gateway(RouteAddress::Inet6(addr)) => gateway = Some(IpAddr::V6(addr)),
618            RouteAttribute::Via(RouteVia::Inet(addr)) => gateway = Some(IpAddr::V4(addr)),
619            RouteAttribute::Via(RouteVia::Inet6(addr)) => gateway = Some(IpAddr::V6(addr)),
620            RouteAttribute::PrefSource(RouteAddress::Inet(addr)) => source = Some(addr),
621            RouteAttribute::Priority(value) => metric = Some(value),
622            RouteAttribute::Oif(index) => oif = Some(index),
623            RouteAttribute::Table(value) => table = Some(value),
624            RouteAttribute::MultiPath(paths) => {
625                nexthops.extend(convert_multipath(paths));
626            }
627            _ => {}
628        }
629    }
630
631    let addr = destination.unwrap_or(Ipv4Addr::UNSPECIFIED);
632    let net = crate::Ipv4Net::new(addr, header.destination_prefix_length).ok()?;
633
634    Some(Ipv4Route {
635        if_id: oif.filter(|id| *id != 0),
636        gateway,
637        source,
638        metric,
639        table,
640        route: net,
641        nexthops,
642    })
643}
644
645fn decode_ipv6_route(message: RouteMessage) -> Option<Ipv6Route> {
646    if message.header.address_family != AddressFamily::Inet6 {
647        return None;
648    }
649
650    let header = message.header;
651    let mut destination = None;
652    let mut gateway: Option<IpAddr> = None;
653    let mut source = None;
654    let mut metric = None;
655    let mut table = table_from_header(header.table);
656    let mut oif = None;
657    let mut nexthops = Vec::new();
658
659    for attr in message.attributes {
660        match attr {
661            RouteAttribute::Destination(RouteAddress::Inet6(addr)) => destination = Some(addr),
662            RouteAttribute::Gateway(RouteAddress::Inet(addr)) => gateway = Some(IpAddr::V4(addr)),
663            RouteAttribute::Gateway(RouteAddress::Inet6(addr)) => gateway = Some(IpAddr::V6(addr)),
664            RouteAttribute::Via(RouteVia::Inet(addr)) => gateway = Some(IpAddr::V4(addr)),
665            RouteAttribute::Via(RouteVia::Inet6(addr)) => gateway = Some(IpAddr::V6(addr)),
666            RouteAttribute::PrefSource(RouteAddress::Inet6(addr)) => source = Some(addr),
667            RouteAttribute::Priority(value) => metric = Some(value),
668            RouteAttribute::Oif(index) => oif = Some(index),
669            RouteAttribute::Table(value) => table = Some(value),
670            RouteAttribute::MultiPath(paths) => {
671                nexthops.extend(convert_multipath(paths));
672            }
673            _ => {}
674        }
675    }
676
677    let addr = destination.unwrap_or(Ipv6Addr::UNSPECIFIED);
678    let net = crate::Ipv6Net::new(addr, header.destination_prefix_length).ok()?;
679
680    Some(Ipv6Route {
681        if_id: oif.filter(|id| *id != 0),
682        gateway,
683        source,
684        metric,
685        table,
686        route: net,
687        nexthops,
688    })
689}
690
691fn table_from_header(value: u8) -> Option<u32> {
692    if value == 0 { None } else { Some(value as u32) }
693}
694
695fn convert_multipath(paths: Vec<RouteNextHop>) -> Vec<RouteNextHopInfo> {
696    let mut result = Vec::new();
697    for path in paths {
698        let mut gateway = None;
699        for attr in path.attributes {
700            match attr {
701                RouteAttribute::Gateway(RouteAddress::Inet(addr)) => {
702                    gateway = Some(IpAddr::V4(addr))
703                }
704                RouteAttribute::Gateway(RouteAddress::Inet6(addr)) => {
705                    gateway = Some(IpAddr::V6(addr))
706                }
707                RouteAttribute::Via(RouteVia::Inet(addr)) => gateway = Some(IpAddr::V4(addr)),
708                RouteAttribute::Via(RouteVia::Inet6(addr)) => gateway = Some(IpAddr::V6(addr)),
709                RouteAttribute::NewDestination(_) => {}
710                _ => {}
711            }
712        }
713
714        let weight = u32::from(path.hops).saturating_add(1);
715        let if_id = match path.interface_index {
716            0 => None,
717            index => Some(index),
718        };
719
720        result.push(RouteNextHopInfo {
721            if_id,
722            gateway,
723            weight,
724            flags: path.flags,
725        });
726    }
727    result
728}
729
730fn build_multipath_v4(entries: &[RouteNextHopInfo]) -> Option<Vec<RouteNextHop>> {
731    let mut nexthops = Vec::new();
732    for entry in entries {
733        let mut route_entry = RouteNextHop::default();
734        route_entry.flags = entry.flags;
735        route_entry.hops = weight_to_hops(entry.weight);
736        route_entry.interface_index = entry.if_id.unwrap_or(0);
737        route_entry.attributes = Vec::new();
738
739        match entry.gateway {
740            Some(IpAddr::V4(addr)) => {
741                route_entry
742                    .attributes
743                    .push(RouteAttribute::Gateway(RouteAddress::Inet(addr)));
744            }
745            Some(IpAddr::V6(addr)) => {
746                route_entry
747                    .attributes
748                    .push(RouteAttribute::Via(RouteVia::Inet6(addr)));
749            }
750            None => {}
751        }
752
753        nexthops.push(route_entry);
754    }
755
756    if nexthops.is_empty() {
757        None
758    } else {
759        Some(nexthops)
760    }
761}
762
763fn build_multipath_v6(entries: &[RouteNextHopInfo]) -> Option<Vec<RouteNextHop>> {
764    let mut nexthops = Vec::new();
765    for entry in entries {
766        let mut route_entry = RouteNextHop::default();
767        route_entry.flags = entry.flags;
768        route_entry.hops = weight_to_hops(entry.weight);
769        route_entry.interface_index = entry.if_id.unwrap_or(0);
770        route_entry.attributes = Vec::new();
771
772        match entry.gateway {
773            Some(IpAddr::V6(addr)) => {
774                route_entry
775                    .attributes
776                    .push(RouteAttribute::Gateway(RouteAddress::Inet6(addr)));
777            }
778            Some(IpAddr::V4(addr)) => {
779                route_entry
780                    .attributes
781                    .push(RouteAttribute::Via(RouteVia::Inet(addr)));
782            }
783            None => {}
784        }
785
786        nexthops.push(route_entry);
787    }
788
789    if nexthops.is_empty() {
790        None
791    } else {
792        Some(nexthops)
793    }
794}
795
796fn weight_to_hops(weight: u32) -> u8 {
797    weight.saturating_sub(1).min(u8::MAX as u32) as u8
798}