ftth_rtnl/
link.rs

1#![allow(unreachable_patterns)]
2
3use ftth_common::channel::{AsyncWorldClient, AsyncWorldServer};
4
5use futures::TryStreamExt;
6
7use std::fmt::{Debug, Display};
8use std::io::{self, ErrorKind};
9
10use netlink_packet_route::link::LinkFlags;
11use rtnetlink::{LinkMessageBuilder, LinkUnspec};
12
13pub(crate) type Client = AsyncWorldClient<RtnlLinkRequest, RtnlLinkResponse>;
14pub(crate) type Server = AsyncWorldServer<RtnlLinkRequest, RtnlLinkResponse>;
15
16#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub struct MacAddr {
18    pub inner: [u8; 6],
19}
20
21impl MacAddr {
22    pub const fn new(inner: [u8; 6]) -> Self {
23        Self { inner }
24    }
25}
26
27impl Default for MacAddr {
28    fn default() -> Self {
29        Self { inner: [0; 6] }
30    }
31}
32
33impl Debug for MacAddr {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.write_str(&format!("MacAddr({})", self))
36    }
37}
38
39impl Display for MacAddr {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.write_str(&format!(
42            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
43            self.inner[0],
44            self.inner[1],
45            self.inner[2],
46            self.inner[3],
47            self.inner[4],
48            self.inner[5],
49        ))
50    }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct Interface {
55    pub if_name: String,
56    pub if_id: u32,
57}
58
59#[derive(Debug, Clone, PartialEq)]
60#[non_exhaustive]
61pub enum RtnlLinkRequest {
62    InterfaceList,
63    InterfaceGet { if_id: u32 },
64    InterfaceGetByName { if_name: String },
65    MacAddrGet { if_id: u32 },
66    MacAddrSet { if_id: u32, mac_addr: MacAddr },
67    MtuGet { if_id: u32 },
68    InterfaceSetAdmin { if_id: u32, up: bool },
69    InterfaceSetPromisc { if_id: u32, enable: bool },
70    InterfaceSetArp { if_id: u32, enable: bool },
71    InterfaceSetMtu { if_id: u32, mtu: u32 },
72    InterfaceRename { if_id: u32, if_name: String },
73    InterfaceSetAllMulticast { if_id: u32, enable: bool },
74}
75
76#[derive(Debug, Clone, PartialEq)]
77#[non_exhaustive]
78pub enum RtnlLinkResponse {
79    Success,
80    Failed,
81    NotImplemented,
82    NotFound,
83    InterfaceList(Vec<Interface>),
84    Interface(Interface),
85    MacAddr(MacAddr),
86    Mtu(u32),
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Hash)]
90pub struct RtnlLinkClient {
91    client: Client,
92}
93
94impl RtnlLinkClient {
95    pub(crate) fn new(client: Client) -> Self {
96        Self { client }
97    }
98
99    pub fn interface_set_up(&self, if_id: u32) -> io::Result<()> {
100        self.interface_set_admin_state(if_id, true)
101    }
102
103    pub fn interface_set_down(&self, if_id: u32) -> io::Result<()> {
104        self.interface_set_admin_state(if_id, false)
105    }
106
107    pub fn interface_set_admin_state(&self, if_id: u32, up: bool) -> io::Result<()> {
108        let res = self
109            .client
110            .send_request(RtnlLinkRequest::InterfaceSetAdmin { if_id, up })?;
111        let op = if up {
112            "Set interface up"
113        } else {
114            "Set interface down"
115        };
116        handle_status_response(op, res)
117    }
118
119    pub fn interface_set_promiscuous(&self, if_id: u32, enable: bool) -> io::Result<()> {
120        let res = self
121            .client
122            .send_request(RtnlLinkRequest::InterfaceSetPromisc { if_id, enable })?;
123        handle_status_response(
124            if enable {
125                "Enable promiscuous mode"
126            } else {
127                "Disable promiscuous mode"
128            },
129            res,
130        )
131    }
132
133    pub fn interface_set_arp(&self, if_id: u32, enable: bool) -> io::Result<()> {
134        let res = self
135            .client
136            .send_request(RtnlLinkRequest::InterfaceSetArp { if_id, enable })?;
137        handle_status_response(if enable { "Enable ARP" } else { "Disable ARP" }, res)
138    }
139
140    pub fn interface_set_mtu(&self, if_id: u32, mtu: u32) -> io::Result<()> {
141        let res = self
142            .client
143            .send_request(RtnlLinkRequest::InterfaceSetMtu { if_id, mtu })?;
144        handle_status_response("Set MTU", res)
145    }
146
147    pub fn interface_rename(&self, if_id: u32, new_name: &str) -> io::Result<()> {
148        let res = self.client.send_request(RtnlLinkRequest::InterfaceRename {
149            if_id,
150            if_name: new_name.to_owned(),
151        })?;
152        handle_status_response("Rename interface", res)
153    }
154
155    pub fn interface_get(&self, if_id: u32) -> io::Result<Interface> {
156        let res = self
157            .client
158            .send_request(RtnlLinkRequest::InterfaceGet { if_id })?;
159        match res {
160            RtnlLinkResponse::Interface(interface) => Ok(interface),
161            RtnlLinkResponse::NotFound => {
162                Err(io::Error::new(ErrorKind::NotFound, "Interface not found"))
163            }
164            _ => Err(io::Error::other("Failed to get interface")),
165        }
166    }
167
168    pub fn interface_get_by_name(&self, name: &str) -> std::io::Result<Interface> {
169        let name = name.to_owned();
170        let res = self
171            .client
172            .send_request(RtnlLinkRequest::InterfaceGetByName { if_name: name })?;
173        match res {
174            RtnlLinkResponse::Interface(interface) => {
175                return Ok(interface);
176            }
177            _ => {}
178        }
179        Err(std::io::Error::other("Not found"))
180    }
181
182    pub fn mac_addr_get(&self, if_id: u32) -> std::io::Result<Option<MacAddr>> {
183        let res = self
184            .client
185            .send_request(RtnlLinkRequest::MacAddrGet { if_id })?;
186        match res {
187            RtnlLinkResponse::MacAddr(addr) => {
188                return Ok(Some(addr));
189            }
190            _ => {}
191        }
192        Ok(None)
193    }
194
195    pub fn mtu_get(&self, if_id: u32) -> io::Result<u32> {
196        let res = self
197            .client
198            .send_request(RtnlLinkRequest::MtuGet { if_id })?;
199        match res {
200            RtnlLinkResponse::Mtu(mtu) => Ok(mtu),
201            RtnlLinkResponse::NotFound => {
202                Err(io::Error::new(ErrorKind::NotFound, "Interface not found"))
203            }
204            _ => Err(io::Error::other("Failed to get MTU")),
205        }
206    }
207
208    pub fn mac_addr_set(&self, if_id: u32, mac_addr: MacAddr) -> io::Result<()> {
209        let res = self
210            .client
211            .send_request(RtnlLinkRequest::MacAddrSet { if_id, mac_addr })?;
212        handle_status_response("Set MAC address", res)
213    }
214
215    pub fn interface_set_all_multicast(&self, if_id: u32, enable: bool) -> io::Result<()> {
216        let res = self
217            .client
218            .send_request(RtnlLinkRequest::InterfaceSetAllMulticast { if_id, enable })?;
219        handle_status_response(
220            if enable {
221                "Enable all-multicast"
222            } else {
223                "Disable all-multicast"
224            },
225            res,
226        )
227    }
228
229    pub fn interface_list(&self) -> std::io::Result<Vec<Interface>> {
230        let res = self.client.send_request(RtnlLinkRequest::InterfaceList)?;
231        match res {
232            RtnlLinkResponse::InterfaceList(list) => {
233                return Ok(list);
234            }
235            _ => {}
236        }
237        Err(std::io::Error::other("Unknown error"))
238    }
239}
240
241fn handle_status_response(op: &str, response: RtnlLinkResponse) -> io::Result<()> {
242    match response {
243        RtnlLinkResponse::Success => Ok(()),
244        RtnlLinkResponse::NotFound => Err(io::Error::new(
245            ErrorKind::NotFound,
246            format!("{}: interface not found", op),
247        )),
248        RtnlLinkResponse::Failed => Err(io::Error::other(format!("{} failed", op))),
249        RtnlLinkResponse::NotImplemented => Err(io::Error::new(
250            ErrorKind::Unsupported,
251            format!("{} not implemented", op),
252        )),
253        other => Err(io::Error::other(format!(
254            "{} returned unexpected response: {:?}",
255            op, other
256        ))),
257    }
258}
259
260async fn apply_link_set<F>(
261    handle: &rtnetlink::LinkHandle,
262    if_id: u32,
263    op: F,
264) -> Result<(), rtnetlink::Error>
265where
266    F: FnOnce(LinkMessageBuilder<LinkUnspec>) -> LinkMessageBuilder<LinkUnspec>,
267{
268    let builder = LinkMessageBuilder::<LinkUnspec>::new().index(if_id);
269    let message = op(builder).build();
270    handle.set(message).execute().await
271}
272
273fn map_link_result(result: Result<(), rtnetlink::Error>, op: &str, if_id: u32) -> RtnlLinkResponse {
274    match result {
275        Ok(()) => RtnlLinkResponse::Success,
276        Err(rtnetlink::Error::NetlinkError(err_msg)) => {
277            let io_err = err_msg.to_io();
278            if io_err.kind() == ErrorKind::NotFound {
279                RtnlLinkResponse::NotFound
280            } else {
281                log::warn!("Failed to {} for ifindex {}: {}", op, if_id, io_err);
282                RtnlLinkResponse::Failed
283            }
284        }
285        Err(err) => {
286            log::warn!("Failed to {} for ifindex {}: {}", op, if_id, err);
287            RtnlLinkResponse::Failed
288        }
289    }
290}
291
292pub(crate) async fn run_server(mut server: Server, mut handle: rtnetlink::LinkHandle) {
293    'reqloop: while let Some((req, respond)) = server.accept().await {
294        match req {
295            RtnlLinkRequest::InterfaceGet { if_id } => {
296                if if_id == 0 {
297                    respond(RtnlLinkResponse::NotFound);
298                    continue 'reqloop;
299                }
300
301                let response = handle.get().match_index(if_id).execute();
302                futures::pin_mut!(response);
303                while let Ok(Some(response)) = response.try_next().await {
304                    let mut if_name = None;
305                    for attr in response.attributes.iter() {
306                        if let netlink_packet_route::link::LinkAttribute::IfName(name) = attr {
307                            if_name = Some(name.clone());
308                        }
309                    }
310
311                    if let Some(name) = if_name {
312                        respond(RtnlLinkResponse::Interface(Interface {
313                            if_id,
314                            if_name: name,
315                        }));
316                        continue 'reqloop;
317                    }
318                }
319                respond(RtnlLinkResponse::NotFound);
320            }
321            RtnlLinkRequest::InterfaceGetByName { if_name } => {
322                let response = handle.get().match_name(if_name.to_owned()).execute();
323                futures::pin_mut!(response);
324                while let Ok(Some(response)) = response.try_next().await {
325                    let if_index = response.header.index;
326                    if if_index == 0 {
327                        continue;
328                    }
329
330                    respond(RtnlLinkResponse::Interface(Interface {
331                        if_id: if_index,
332                        if_name: if_name.to_owned(),
333                    }));
334                    continue 'reqloop;
335                }
336                respond(RtnlLinkResponse::NotFound);
337            }
338            RtnlLinkRequest::MacAddrGet { if_id } => {
339                let if_index = if_id;
340                if if_index == 0 {
341                    respond(RtnlLinkResponse::NotFound);
342                    continue 'reqloop;
343                }
344                let response = handle.get().match_index(if_index).execute();
345                futures::pin_mut!(response);
346                while let Ok(Some(response)) = response.try_next().await {
347                    for link in response.attributes.iter() {
348                        match link {
349                            netlink_packet_route::link::LinkAttribute::Address(addr) => {
350                                if addr.len() < 6 {
351                                    continue;
352                                }
353                                let mut mac_bytes = [0u8; 6];
354                                mac_bytes.copy_from_slice(&addr[..6]);
355                                respond(RtnlLinkResponse::MacAddr(MacAddr::new(mac_bytes)));
356                                continue 'reqloop;
357                            }
358                            _ => {}
359                        }
360                    }
361                }
362                respond(RtnlLinkResponse::NotFound);
363            }
364            RtnlLinkRequest::MtuGet { if_id } => {
365                if if_id == 0 {
366                    respond(RtnlLinkResponse::NotFound);
367                    continue 'reqloop;
368                }
369
370                let response = handle.get().match_index(if_id).execute();
371                futures::pin_mut!(response);
372                while let Ok(Some(response)) = response.try_next().await {
373                    for link in response.attributes.iter() {
374                        if let netlink_packet_route::link::LinkAttribute::Mtu(mtu) = link {
375                            respond(RtnlLinkResponse::Mtu(*mtu));
376                            continue 'reqloop;
377                        }
378                    }
379                }
380                respond(RtnlLinkResponse::NotFound);
381            }
382            RtnlLinkRequest::InterfaceList => {
383                let mut interfaces = Vec::new();
384                let response = handle.get().execute();
385                futures::pin_mut!(response);
386                while let Ok(Some(response)) = response.try_next().await {
387                    let if_index = response.header.index;
388                    let mut if_name = None;
389                    for link in response.attributes.iter() {
390                        match link {
391                            netlink_packet_route::link::LinkAttribute::IfName(name) => {
392                                if_name = Some(name.clone());
393                            }
394                            _ => {}
395                        }
396                    }
397
398                    if let Some(name) = if_name {
399                        if if_index == 0 {
400                            continue;
401                        }
402
403                        interfaces.push(Interface {
404                            if_id: if_index,
405                            if_name: name,
406                        });
407                    }
408                }
409                respond(RtnlLinkResponse::InterfaceList(interfaces));
410            }
411            RtnlLinkRequest::MacAddrSet { if_id, mac_addr } => {
412                if if_id == 0 {
413                    respond(RtnlLinkResponse::NotFound);
414                    continue 'reqloop;
415                }
416
417                let mac_bytes = mac_addr.inner.to_vec();
418                let result =
419                    apply_link_set(&handle, if_id, |builder| builder.address(mac_bytes)).await;
420                respond(map_link_result(result, "set MAC address", if_id));
421            }
422            RtnlLinkRequest::InterfaceSetAdmin { if_id, up } => {
423                if if_id == 0 {
424                    respond(RtnlLinkResponse::NotFound);
425                    continue 'reqloop;
426                }
427
428                let op_desc = if up {
429                    "set interface up"
430                } else {
431                    "set interface down"
432                };
433                let result = apply_link_set(&handle, if_id, |builder| {
434                    if up { builder.up() } else { builder.down() }
435                })
436                .await;
437
438                respond(map_link_result(result, op_desc, if_id));
439            }
440            RtnlLinkRequest::InterfaceSetPromisc { if_id, enable } => {
441                if if_id == 0 {
442                    respond(RtnlLinkResponse::NotFound);
443                    continue 'reqloop;
444                }
445
446                let op_desc = if enable {
447                    "enable promiscuous mode"
448                } else {
449                    "disable promiscuous mode"
450                };
451                let result =
452                    apply_link_set(&handle, if_id, |builder| builder.promiscuous(enable)).await;
453
454                respond(map_link_result(result, op_desc, if_id));
455            }
456            RtnlLinkRequest::InterfaceSetArp { if_id, enable } => {
457                if if_id == 0 {
458                    respond(RtnlLinkResponse::NotFound);
459                    continue 'reqloop;
460                }
461
462                let op_desc = if enable { "enable ARP" } else { "disable ARP" };
463                let result = apply_link_set(&handle, if_id, |builder| builder.arp(enable)).await;
464
465                respond(map_link_result(result, op_desc, if_id));
466            }
467            RtnlLinkRequest::InterfaceSetMtu { if_id, mtu } => {
468                if if_id == 0 {
469                    respond(RtnlLinkResponse::NotFound);
470                    continue 'reqloop;
471                }
472
473                let result = apply_link_set(&handle, if_id, |builder| builder.mtu(mtu)).await;
474                respond(map_link_result(result, "set MTU", if_id));
475            }
476            RtnlLinkRequest::InterfaceRename { if_id, if_name } => {
477                if if_id == 0 {
478                    respond(RtnlLinkResponse::NotFound);
479                    continue 'reqloop;
480                }
481
482                let new_name = if_name.clone();
483                let result = apply_link_set(&handle, if_id, |builder| builder.name(new_name)).await;
484                let op_desc = format!("rename interface to {}", if_name);
485                respond(map_link_result(result, &op_desc, if_id));
486            }
487            RtnlLinkRequest::InterfaceSetAllMulticast { if_id, enable } => {
488                if if_id == 0 {
489                    respond(RtnlLinkResponse::NotFound);
490                    continue 'reqloop;
491                }
492
493                let op_desc = if enable {
494                    "enable all-multicast mode"
495                } else {
496                    "disable all-multicast mode"
497                };
498
499                let mut message = LinkMessageBuilder::<LinkUnspec>::new().index(if_id).build();
500                if enable {
501                    message.header.flags |= LinkFlags::Allmulti;
502                } else {
503                    message.header.flags.remove(LinkFlags::Allmulti);
504                }
505                message.header.change_mask |= LinkFlags::Allmulti;
506
507                let result = handle.set(message).execute().await;
508
509                respond(map_link_result(result, op_desc, if_id));
510            }
511            _ => respond(RtnlLinkResponse::NotImplemented),
512        }
513    }
514}