innernet_shared/
netlink.rs

1use ipnet::IpNet;
2use netlink_packet_core::{
3    NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_REPLACE,
4    NLM_F_REQUEST,
5};
6use netlink_packet_route::{
7    address::{self, AddressHeader, AddressMessage},
8    link::{self, LinkFlags, LinkHeader, LinkMessage, State},
9    route::{self, RouteHeader, RouteMessage},
10    AddressFamily, RouteNetlinkMessage,
11};
12use netlink_request::netlink_request_rtnl;
13use std::{io, net::IpAddr};
14use wireguard_control::InterfaceName;
15
16fn if_nametoindex(interface: &InterfaceName) -> Result<u32, io::Error> {
17    match unsafe { libc::if_nametoindex(interface.as_ptr()) } {
18        0 => Err(io::Error::new(
19            io::ErrorKind::NotFound,
20            format!("couldn't find interface '{interface}'."),
21        )),
22        index => Ok(index),
23    }
24}
25
26pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), io::Error> {
27    let index = if_nametoindex(interface)?;
28    let header = LinkHeader {
29        index,
30        flags: LinkFlags::Up,
31        ..Default::default()
32    };
33    let mut message = LinkMessage::default();
34    message.header = header;
35    message.attributes = vec![link::LinkAttribute::Mtu(mtu)];
36    netlink_request_rtnl(RouteNetlinkMessage::SetLink(message), None)?;
37    log::debug!("set interface {} up with mtu {}", interface, mtu);
38    Ok(())
39}
40
41pub fn set_addr(interface: &InterfaceName, addr: IpNet) -> Result<(), io::Error> {
42    let index = if_nametoindex(interface)?;
43    let (family, nlas) = match addr {
44        IpNet::V4(network) => {
45            let addr = IpAddr::V4(network.addr());
46            (
47                AddressFamily::Inet,
48                vec![
49                    address::AddressAttribute::Local(addr),
50                    address::AddressAttribute::Address(addr),
51                ],
52            )
53        },
54        IpNet::V6(network) => (
55            AddressFamily::Inet6,
56            vec![address::AddressAttribute::Address(IpAddr::V6(
57                network.addr(),
58            ))],
59        ),
60    };
61    let header = AddressHeader {
62        index,
63        family,
64        prefix_len: addr.prefix_len(),
65        scope: address::AddressScope::Universe,
66        ..Default::default()
67    };
68
69    let mut message = AddressMessage::default();
70    message.header = header;
71    message.attributes = nlas;
72    netlink_request_rtnl(
73        RouteNetlinkMessage::NewAddress(message),
74        Some(NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE | NLM_F_CREATE),
75    )?;
76    log::debug!("set address {} on interface {}", addr, interface);
77    Ok(())
78}
79
80pub fn add_route(interface: &InterfaceName, cidr: IpNet) -> Result<bool, io::Error> {
81    let if_index = if_nametoindex(interface)?;
82    let (address_family, dst) = match cidr {
83        IpNet::V4(network) => (
84            AddressFamily::Inet,
85            route::RouteAttribute::Destination(route::RouteAddress::Inet(network.network())),
86        ),
87        IpNet::V6(network) => (
88            AddressFamily::Inet6,
89            route::RouteAttribute::Destination(route::RouteAddress::Inet6(network.network())),
90        ),
91    };
92    let header = RouteHeader {
93        table: RouteHeader::RT_TABLE_MAIN,
94        protocol: route::RouteProtocol::Boot,
95        scope: route::RouteScope::Link,
96        kind: route::RouteType::Unicast,
97        destination_prefix_length: cidr.prefix_len(),
98        address_family,
99        ..Default::default()
100    };
101    let mut message = RouteMessage::default();
102    message.header = header;
103    message.attributes = vec![dst, route::RouteAttribute::Oif(if_index)];
104
105    match netlink_request_rtnl(RouteNetlinkMessage::NewRoute(message), None) {
106        Ok(_) => {
107            log::debug!("added route {} to interface {}", cidr, interface);
108            Ok(true)
109        },
110        Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
111            log::debug!("route {} already existed.", cidr);
112            Ok(false)
113        },
114        Err(e) => Err(e),
115    }
116}
117
118fn get_links() -> Result<Vec<String>, io::Error> {
119    let link_responses = netlink_request_rtnl(
120        RouteNetlinkMessage::GetLink(LinkMessage::default()),
121        Some(NLM_F_DUMP | NLM_F_REQUEST),
122    )?;
123    let links = link_responses
124        .into_iter()
125        // Filter out non-link messages
126        .filter_map(|response| match response {
127            NetlinkMessage {
128                payload: NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(link)),
129                ..
130            } => Some(link),
131            _ => None,
132        })
133        // Filter out loopback links
134        .filter_map(|link| if !link.header.flags.contains(LinkFlags::Loopback) {
135                Some(link.attributes)
136            } else {
137                None
138            })
139        // Find and filter out addresses for interfaces
140        .filter(|nlas| nlas.iter().any(|nla| nla == &link::LinkAttribute::OperState(State::Up)))
141        .filter_map(|nlas| nlas.iter().find_map(|nla| match nla {
142            link::LinkAttribute::IfName(name) => Some(name.clone()),
143            _ => None,
144        }))
145        .collect::<Vec<_>>();
146
147    Ok(links)
148}
149
150pub fn get_local_addrs() -> Result<impl Iterator<Item = IpAddr>, io::Error> {
151    let links = get_links()?;
152    let addr_responses = netlink_request_rtnl(
153        RouteNetlinkMessage::GetAddress(AddressMessage::default()),
154        Some(NLM_F_DUMP | NLM_F_REQUEST),
155    )?;
156    let addrs = addr_responses
157        .into_iter()
158        // Filter out non-link messages
159        .filter_map(|response| match response {
160            NetlinkMessage {
161                payload: NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(addr)),
162                ..
163            } => Some(addr),
164            _ => None,
165        })
166        // Filter out non-global-scoped addresses
167        .filter_map(|link| if link.header.scope == address::AddressScope::Universe {
168                Some(link.attributes)
169            } else {
170                None
171            })
172        // Only select addresses for helpful links
173        .filter(move |nlas| nlas.iter().any(|nla| {
174            matches!(nla, address::AddressAttribute::Label(label) if links.contains(label))
175            || matches!(nla, address::AddressAttribute::Address(IpAddr::V6(_addr)))
176        }))
177        .filter_map(|nlas| nlas.iter().find_map(|nla| match nla {
178            address::AddressAttribute::Address(IpAddr::V4(addr)) => Some(IpAddr::V4(*addr)),
179            address::AddressAttribute::Address(IpAddr::V6(addr)) => Some(IpAddr::V6(*addr)),
180            _ => None,
181        }));
182    Ok(addrs)
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_local_addrs() {
191        let addrs = get_local_addrs().unwrap();
192        println!("{:?}", addrs.collect::<Vec<_>>());
193    }
194}