use std::collections::HashMap;
use futures::stream::TryStreamExt;
use netlink_packet_route::route::{
self as rt, RouteAddress, RouteAttribute, RouteMessage, RouteMetric,
RouteVia,
};
use netlink_sys::AsyncSocket;
use rtnetlink::{new_connection, IpVersion};
use serde::{Deserialize, Serialize};
use super::super::filter::{apply_kernel_route_filter, should_drop_by_filter};
use crate::{NetStateRouteFilter, NisporError};
const USER_HZ: u32 = 100;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[non_exhaustive]
pub struct Route {
pub address_family: AddressFamily,
pub tos: u8,
pub table: u32,
pub protocol: RouteProtocol,
pub scope: RouteScope,
pub route_type: RouteType,
pub flags: Vec<RouteFlag>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dst: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub oif: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iif: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prefered_src: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub src: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub realm: Option<RouteRealm>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub via: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mark: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uid: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lock: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mtu: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rtt: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rttvar: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssthresh: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cwnd: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub advmss: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reordering: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hoplimit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub initcwnd: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub features: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rto_min: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub initrwnd: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quickack: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cc_algo: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fastopen_no_cookie: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_clntref: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_last_use: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_expires: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_error: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_used: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_id: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_ts: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_ts_age: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metric: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preference: Option<RoutePreference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multipath: Option<Vec<MultipathRoute>>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub enum AddressFamily {
IPv4,
IPv6,
Other(u8),
#[default]
Unknown,
}
impl From<netlink_packet_route::AddressFamily> for AddressFamily {
fn from(d: netlink_packet_route::AddressFamily) -> Self {
match d {
netlink_packet_route::AddressFamily::Inet => AddressFamily::IPv4,
netlink_packet_route::AddressFamily::Inet6 => AddressFamily::IPv6,
_ => Self::Other(u8::from(d)),
}
}
}
impl From<AddressFamily> for netlink_packet_route::AddressFamily {
fn from(v: AddressFamily) -> Self {
match v {
AddressFamily::IPv4 => netlink_packet_route::AddressFamily::Inet,
AddressFamily::IPv6 => netlink_packet_route::AddressFamily::Inet6,
AddressFamily::Other(d) => d.into(),
AddressFamily::Unknown => {
netlink_packet_route::AddressFamily::Unspec
}
}
}
}
#[derive(
Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Default,
)]
#[serde(rename_all = "lowercase")]
pub enum RouteProtocol {
#[default]
Unspec,
#[serde(rename = "icmp_redirect")]
IcmpRedirect,
Kernel,
Boot,
Static,
Gated,
Ra,
#[serde(rename = "merit_mrt")]
Mrt,
Zebra,
Bird,
#[serde(rename = "decnet_routing_daemon")]
DnRouted,
Xorp,
#[serde(rename = "netsukuku")]
Ntk,
Dhcp,
#[serde(rename = "multicast_daemon")]
Mrouted,
#[serde(rename = "keepalived_daemon")]
KeepAlived,
Babel,
Bgp,
Isis,
Ospf,
Rip,
Eigrp,
Unknown,
Other(u8),
}
impl From<rt::RouteProtocol> for RouteProtocol {
fn from(d: rt::RouteProtocol) -> Self {
match d {
rt::RouteProtocol::Unspec => RouteProtocol::Unspec,
rt::RouteProtocol::IcmpRedirect => RouteProtocol::IcmpRedirect,
rt::RouteProtocol::Kernel => RouteProtocol::Kernel,
rt::RouteProtocol::Boot => RouteProtocol::Boot,
rt::RouteProtocol::Static => RouteProtocol::Static,
rt::RouteProtocol::Gated => RouteProtocol::Gated,
rt::RouteProtocol::Ra => RouteProtocol::Ra,
rt::RouteProtocol::Mrt => RouteProtocol::Mrt,
rt::RouteProtocol::Zebra => RouteProtocol::Zebra,
rt::RouteProtocol::Bird => RouteProtocol::Bird,
rt::RouteProtocol::DnRouted => RouteProtocol::DnRouted,
rt::RouteProtocol::Xorp => RouteProtocol::Xorp,
rt::RouteProtocol::Ntk => RouteProtocol::Ntk,
rt::RouteProtocol::Dhcp => RouteProtocol::Dhcp,
rt::RouteProtocol::Mrouted => RouteProtocol::Mrouted,
rt::RouteProtocol::KeepAlived => RouteProtocol::KeepAlived,
rt::RouteProtocol::Babel => RouteProtocol::Babel,
rt::RouteProtocol::Bgp => RouteProtocol::Bgp,
rt::RouteProtocol::Isis => RouteProtocol::Isis,
rt::RouteProtocol::Ospf => RouteProtocol::Ospf,
rt::RouteProtocol::Rip => RouteProtocol::Rip,
rt::RouteProtocol::Eigrp => RouteProtocol::Eigrp,
_ => RouteProtocol::Other(d.into()),
}
}
}
impl From<RouteProtocol> for rt::RouteProtocol {
fn from(v: RouteProtocol) -> Self {
match v {
RouteProtocol::Unspec => rt::RouteProtocol::Unspec,
RouteProtocol::IcmpRedirect => rt::RouteProtocol::IcmpRedirect,
RouteProtocol::Kernel => rt::RouteProtocol::Kernel,
RouteProtocol::Boot => rt::RouteProtocol::Boot,
RouteProtocol::Static => rt::RouteProtocol::Static,
RouteProtocol::Gated => rt::RouteProtocol::Gated,
RouteProtocol::Ra => rt::RouteProtocol::Ra,
RouteProtocol::Mrt => rt::RouteProtocol::Mrt,
RouteProtocol::Zebra => rt::RouteProtocol::Zebra,
RouteProtocol::Bird => rt::RouteProtocol::Bird,
RouteProtocol::DnRouted => rt::RouteProtocol::DnRouted,
RouteProtocol::Xorp => rt::RouteProtocol::Xorp,
RouteProtocol::Ntk => rt::RouteProtocol::Ntk,
RouteProtocol::Dhcp => rt::RouteProtocol::Dhcp,
RouteProtocol::Mrouted => rt::RouteProtocol::Mrouted,
RouteProtocol::KeepAlived => rt::RouteProtocol::KeepAlived,
RouteProtocol::Babel => rt::RouteProtocol::Babel,
RouteProtocol::Bgp => rt::RouteProtocol::Bgp,
RouteProtocol::Isis => rt::RouteProtocol::Isis,
RouteProtocol::Ospf => rt::RouteProtocol::Ospf,
RouteProtocol::Rip => rt::RouteProtocol::Rip,
RouteProtocol::Eigrp => rt::RouteProtocol::Eigrp,
RouteProtocol::Unknown => rt::RouteProtocol::Unspec,
RouteProtocol::Other(d) => d.into(),
}
}
}
impl From<&str> for RouteProtocol {
fn from(v: &str) -> Self {
match v {
"icmp_redirect" => RouteProtocol::IcmpRedirect,
"kernel" => RouteProtocol::Kernel,
"boot" => RouteProtocol::Boot,
"static" => RouteProtocol::Static,
"gated" => RouteProtocol::Gated,
"ra" => RouteProtocol::Ra,
"merit_mrt" => RouteProtocol::Mrt,
"zebra" => RouteProtocol::Zebra,
"bird" => RouteProtocol::Bird,
"decnet_routing_daemon" => RouteProtocol::DnRouted,
"xorp" => RouteProtocol::Xorp,
"netsukuku" => RouteProtocol::Ntk,
"Dhcp" => RouteProtocol::Dhcp,
"multicast_daemon" => RouteProtocol::Mrouted,
"keepalived_daemon" => RouteProtocol::KeepAlived,
"babel" => RouteProtocol::Babel,
"bgp" => RouteProtocol::Bgp,
"isis" => RouteProtocol::Isis,
"ospf" => RouteProtocol::Ospf,
"rip" => RouteProtocol::Rip,
"eigrp" => RouteProtocol::Eigrp,
_ => RouteProtocol::Unknown,
}
}
}
#[derive(
Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Default,
)]
#[serde(rename_all = "lowercase")]
pub enum RouteScope {
Universe,
Site,
Link,
Host,
#[serde(rename = "no_where")]
NoWhere,
#[default]
Unknown,
Other(u8),
}
impl From<rt::RouteScope> for RouteScope {
fn from(d: rt::RouteScope) -> Self {
match d {
rt::RouteScope::Universe => RouteScope::Universe,
rt::RouteScope::Site => RouteScope::Site,
rt::RouteScope::Link => RouteScope::Link,
rt::RouteScope::Host => RouteScope::Host,
rt::RouteScope::NoWhere => RouteScope::NoWhere,
_ => RouteScope::Other(d.into()),
}
}
}
impl From<RouteScope> for rt::RouteScope {
fn from(v: RouteScope) -> rt::RouteScope {
match v {
RouteScope::Universe => rt::RouteScope::Universe,
RouteScope::Site => rt::RouteScope::Site,
RouteScope::Link => rt::RouteScope::Link,
RouteScope::Host => rt::RouteScope::Host,
RouteScope::NoWhere => rt::RouteScope::NoWhere,
RouteScope::Unknown => rt::RouteScope::Universe,
RouteScope::Other(d) => d.into(),
}
}
}
impl std::fmt::Display for RouteScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Universe => write!(f, "universe"),
Self::Site => write!(f, "site"),
Self::Link => write!(f, "link"),
Self::Host => write!(f, "host"),
Self::NoWhere => write!(f, "no_where"),
Self::Unknown => write!(f, "unknown"),
Self::Other(s) => write!(f, "{s}"),
}
}
}
impl From<&str> for RouteScope {
fn from(v: &str) -> Self {
match v {
"u" | "universe" | "g" | "global" => RouteScope::Universe,
"s" | "site" => RouteScope::Site,
"l" | "link" => RouteScope::Link,
"h" | "host" => RouteScope::Host,
"n" | "nowhere" | "no_where" => RouteScope::NoWhere,
_ => RouteScope::Unknown,
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub enum RouteType {
#[default]
Unspec,
Unicast,
Local,
Broadcast,
Anycast,
Multicast,
BlackHole,
Unreachable,
Prohibit,
Throw,
Nat,
ExternalResolve,
Unknown,
Other(u8),
}
impl From<rt::RouteType> for RouteType {
fn from(d: rt::RouteType) -> Self {
match d {
rt::RouteType::Unspec => RouteType::Unspec,
rt::RouteType::Unicast => RouteType::Unicast,
rt::RouteType::Local => RouteType::Local,
rt::RouteType::Broadcast => RouteType::Broadcast,
rt::RouteType::Anycast => RouteType::Anycast,
rt::RouteType::Multicast => RouteType::Multicast,
rt::RouteType::BlackHole => RouteType::BlackHole,
rt::RouteType::Unreachable => RouteType::Unreachable,
rt::RouteType::Prohibit => RouteType::Prohibit,
rt::RouteType::Throw => RouteType::Throw,
rt::RouteType::Nat => RouteType::Nat,
rt::RouteType::ExternalResolve => RouteType::ExternalResolve,
_ => RouteType::Other(d.into()),
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub struct MultipathRoute {
pub via: String,
pub iface: String,
pub weight: u16, pub flags: Vec<MultipathRouteFlags>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename_all = "lowercase")]
pub enum MultipathRouteFlags {
Dead,
Pervasive,
#[serde(rename = "on_link")]
OnLink,
Offload,
#[serde(rename = "link_down")]
LinkDown,
Unresolved,
Trap,
Other(u8),
}
impl From<rt::RouteNextHopFlag> for MultipathRouteFlags {
fn from(d: rt::RouteNextHopFlag) -> Self {
match d {
rt::RouteNextHopFlag::Dead => Self::Dead,
rt::RouteNextHopFlag::Pervasive => Self::Pervasive,
rt::RouteNextHopFlag::Onlink => Self::OnLink,
rt::RouteNextHopFlag::Offload => Self::Offload,
rt::RouteNextHopFlag::Linkdown => Self::LinkDown,
rt::RouteNextHopFlag::Unresolved => Self::Unresolved,
rt::RouteNextHopFlag::Trap => Self::Trap,
_ => Self::Other(u8::from(d)),
}
}
}
pub(crate) async fn get_routes(
iface_name2index: &HashMap<String, u32>,
filter: Option<&NetStateRouteFilter>,
) -> Result<Vec<Route>, NisporError> {
let mut routes = Vec::new();
let mut has_kernel_filter = true;
let (mut connection, handle, _) = new_connection()?;
let mut ifindex_to_name = HashMap::new();
for (name, index) in iface_name2index.iter() {
ifindex_to_name.insert(format!("{index}"), name.to_string());
}
if filter.is_some() {
if let Err(e) = connection
.socket_mut()
.socket_mut()
.set_netlink_get_strict_chk(true)
{
log::warn!(
"Failed to set kernel space route filter: {e}, \
falling back to user space route filtering which would \
lead to performance penalty"
);
has_kernel_filter = false;
}
}
tokio::spawn(connection);
for ip_family in [IpVersion::V6, IpVersion::V4] {
let mut rt_handle = handle.route().get(ip_family);
if let Some(filter) = filter {
if has_kernel_filter {
apply_kernel_route_filter(
&mut rt_handle,
filter,
iface_name2index,
)?;
}
}
let mut links = rt_handle.execute();
while let Some(rt_msg) = links.try_next().await? {
let route = get_route(rt_msg, &ifindex_to_name)?;
if let Some(filter) = filter {
if should_drop_by_filter(&route, filter, has_kernel_filter) {
continue;
}
}
routes.push(route);
}
}
Ok(routes)
}
fn get_route(
route_msg: RouteMessage,
ifindex_to_name: &HashMap<String, String>,
) -> Result<Route, NisporError> {
let mut rt = Route::default();
let header = &route_msg.header;
rt.address_family = header.address_family.into();
let src_prefix_len = header.source_prefix_length;
let dst_prefix_len = header.destination_prefix_length;
rt.table = header.table.into();
rt.tos = header.tos;
rt.protocol = header.protocol.into();
rt.scope = header.scope.into();
rt.flags = header
.flags
.as_slice()
.iter()
.map(|f| RouteFlag::from(*f))
.collect();
rt.route_type = header.kind.into();
let _family = &rt.address_family;
for nla in &route_msg.attributes {
match nla {
RouteAttribute::Destination(d) => {
rt.dst = Some(format!(
"{}/{}",
_rt_addr_to_string(d),
dst_prefix_len
));
}
RouteAttribute::Oif(d) => {
rt.oif = if let Some(iface_name) =
ifindex_to_name.get(&format!("{d}"))
{
Some(iface_name.clone())
} else {
Some(format!("{d}"))
}
}
RouteAttribute::PrefSource(d) => {
rt.prefered_src = Some(_rt_addr_to_string(d));
}
RouteAttribute::Table(d) => {
rt.table = *d;
}
RouteAttribute::Realm(d) => {
rt.realm = Some((*d).into());
}
RouteAttribute::Source(d) => {
rt.src = Some(format!(
"{}/{}",
_rt_addr_to_string(d),
src_prefix_len
));
}
RouteAttribute::Gateway(d) => {
rt.gateway = Some(_rt_addr_to_string(d));
}
RouteAttribute::Via(d) => {
if let RouteVia::Inet(a) = d {
rt.via = Some(a.to_string());
} else if let RouteVia::Inet6(a) = d {
rt.via = Some(a.to_string());
}
}
RouteAttribute::Metrics(nlas) => {
for nla in nlas {
match nla {
RouteMetric::Lock(d) => {
rt.lock = Some(*d);
}
RouteMetric::Mtu(d) => {
rt.mtu = Some(*d);
}
RouteMetric::Window(d) => {
rt.window = Some(*d);
}
RouteMetric::Rtt(d) => {
rt.rtt = Some(*d);
}
RouteMetric::RttVar(d) => {
rt.rttvar = Some(*d);
}
RouteMetric::SsThresh(d) => {
rt.ssthresh = Some(*d);
}
RouteMetric::Cwnd(d) => {
rt.cwnd = Some(*d);
}
RouteMetric::Advmss(d) => {
rt.advmss = Some(*d);
}
RouteMetric::Reordering(d) => {
rt.reordering = Some(*d);
}
RouteMetric::Hoplimit(d) => {
rt.hoplimit = Some(*d);
}
RouteMetric::InitCwnd(d) => {
rt.initcwnd = Some(*d);
}
RouteMetric::Features(d) => {
rt.features = Some(*d);
}
RouteMetric::RtoMin(d) => {
rt.rto_min = Some(*d);
}
RouteMetric::InitRwnd(d) => {
rt.initrwnd = Some(*d);
}
RouteMetric::QuickAck(d) => {
rt.quickack = Some(*d);
}
RouteMetric::CcAlgo(d) => {
rt.cc_algo = Some(*d);
}
RouteMetric::FastopenNoCookie(d) => {
rt.fastopen_no_cookie = Some(*d);
}
_ => {
log::debug!(
"Unknown RTA_METRICS message {:?}",
nla
);
}
}
}
}
RouteAttribute::Mark(d) => {
rt.mark = Some(*d);
}
RouteAttribute::Uid(d) => {
rt.uid = Some(*d);
}
RouteAttribute::Iif(d) => {
rt.iif = if let Some(iface_name) =
ifindex_to_name.get(&format!("{d}"))
{
Some(iface_name.clone())
} else {
Some(format!("{d}"))
}
}
RouteAttribute::CacheInfo(d) => {
rt.cache_clntref = Some(d.clntref);
rt.cache_last_use = Some(d.last_use);
rt.cache_expires = Some(d.expires / USER_HZ);
rt.cache_error = Some(d.error);
rt.cache_used = Some(d.used);
rt.cache_id = Some(d.id);
rt.cache_ts = Some(d.ts);
rt.cache_ts_age = Some(d.ts_age);
}
RouteAttribute::Priority(d) => {
rt.metric = Some(*d);
}
RouteAttribute::MultiPath(hops) => {
let mut next_hops = Vec::new();
for hop in hops.as_slice() {
let mut mp_rt = MultipathRoute::default();
for nla in hop.attributes.iter() {
if let RouteAttribute::Gateway(v) = nla {
if let RouteAddress::Inet(v) = v {
mp_rt.via = v.to_string();
} else if let RouteAddress::Inet6(v) = v {
mp_rt.via = v.to_string();
}
break;
}
}
let iface_index = hop.interface_index;
mp_rt.iface = if let Some(iface_name) =
ifindex_to_name.get(&format!("{iface_index}"))
{
iface_name.clone()
} else {
format!("{iface_index}")
};
mp_rt.flags = hop
.flags
.as_slice()
.iter()
.map(|f| MultipathRouteFlags::from(*f))
.collect();
mp_rt.weight = u16::from(hop.hops) + 1;
next_hops.push(mp_rt);
}
rt.multipath = Some(next_hops);
}
RouteAttribute::Preference(d) => rt.preference = Some((*d).into()),
_ => log::debug!("Unknown NLA message for route {:?}", nla),
}
}
Ok(rt)
}
fn _rt_addr_to_string(addr: &RouteAddress) -> String {
match addr {
RouteAddress::Inet(v) => v.to_string(),
RouteAddress::Inet6(v) => v.to_string(),
_ => {
log::debug!("Unknown RouteAddress type {:?}", addr);
String::new()
}
}
}
#[derive(
Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize,
)]
#[non_exhaustive]
#[serde(rename_all = "lowercase")]
pub enum RoutePreference {
Low,
#[default]
Medium,
High,
Invalid,
Other(u8),
}
impl From<rt::RoutePreference> for RoutePreference {
fn from(d: rt::RoutePreference) -> Self {
match d {
rt::RoutePreference::Low => Self::Low,
rt::RoutePreference::Medium => Self::Medium,
rt::RoutePreference::High => Self::High,
rt::RoutePreference::Invalid => Self::Invalid,
_ => Self::Other(d.into()),
}
}
}
#[derive(
Clone, Eq, PartialEq, Debug, Copy, Default, Serialize, Deserialize,
)]
#[serde(rename_all = "lowercase")]
pub struct RouteRealm {
pub source: u16,
pub destination: u16,
}
impl From<rt::RouteRealm> for RouteRealm {
fn from(d: rt::RouteRealm) -> Self {
Self {
source: d.source,
destination: d.destination,
}
}
}
#[derive(Clone, Eq, PartialEq, Debug, Copy, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(rename_all = "snake_case")]
pub enum RouteFlag {
Dead,
Pervasive,
Onlink,
Offload,
Linkdown,
Unresolved,
Trap,
Notify,
Cloned,
Equalize,
Prefix,
LookupTable,
FibMatch,
RtOffload,
RtTrap,
OffloadFailed,
Other(u32),
}
impl From<rt::RouteFlag> for RouteFlag {
fn from(d: rt::RouteFlag) -> Self {
match d {
rt::RouteFlag::Dead => Self::Dead,
rt::RouteFlag::Pervasive => Self::Pervasive,
rt::RouteFlag::Onlink => Self::Onlink,
rt::RouteFlag::Offload => Self::Offload,
rt::RouteFlag::Linkdown => Self::Linkdown,
rt::RouteFlag::Unresolved => Self::Unresolved,
rt::RouteFlag::Trap => Self::Trap,
rt::RouteFlag::Notify => Self::Notify,
rt::RouteFlag::Cloned => Self::Cloned,
rt::RouteFlag::Equalize => Self::Equalize,
rt::RouteFlag::Prefix => Self::Prefix,
rt::RouteFlag::LookupTable => Self::LookupTable,
rt::RouteFlag::FibMatch => Self::FibMatch,
rt::RouteFlag::RtOffload => Self::RtOffload,
rt::RouteFlag::RtTrap => Self::RtTrap,
rt::RouteFlag::OffloadFailed => Self::OffloadFailed,
_ => Self::Other(d.into()),
}
}
}