Skip to main content

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, LinkHeader, LinkLayerType};
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    pub link_layer_type: LinkLayerType,
58}
59
60#[derive(Debug, Clone, PartialEq)]
61#[non_exhaustive]
62pub enum RtnlLinkRequest {
63    InterfaceList,
64    InterfaceGet {
65        if_id: u32,
66    },
67    InterfaceGetByName {
68        if_name: String,
69    },
70    MacAddrGet {
71        if_id: u32,
72    },
73    MacAddrSet {
74        if_id: u32,
75        link_layer_type: LinkLayerType,
76        mac_addr: MacAddr,
77    },
78    MtuGet {
79        if_id: u32,
80    },
81    InterfaceSetAdmin {
82        if_id: u32,
83        up: bool,
84    },
85    InterfaceSetPromisc {
86        if_id: u32,
87        enable: bool,
88    },
89    InterfaceSetArp {
90        if_id: u32,
91        enable: bool,
92    },
93    InterfaceSetMtu {
94        if_id: u32,
95        mtu: u32,
96    },
97    InterfaceRename {
98        if_id: u32,
99        if_name: String,
100    },
101    InterfaceSetAllMulticast {
102        if_id: u32,
103        enable: bool,
104    },
105}
106
107#[derive(Debug, Clone, PartialEq)]
108#[non_exhaustive]
109pub enum RtnlLinkResponse {
110    Success,
111    Failed,
112    FailedWithMessage(String),
113    NotImplemented,
114    NotFound,
115    InterfaceList(Vec<Interface>),
116    Interface(Interface),
117    MacAddr(MacAddr),
118    Mtu(u32),
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122pub struct RtnlLinkClient {
123    client: Client,
124}
125
126impl RtnlLinkClient {
127    pub(crate) fn new(client: Client) -> Self {
128        Self { client }
129    }
130
131    pub fn interface_set_up(&self, if_id: u32) -> io::Result<()> {
132        self.interface_set_admin_state(if_id, true)
133    }
134
135    pub fn interface_set_down(&self, if_id: u32) -> io::Result<()> {
136        self.interface_set_admin_state(if_id, false)
137    }
138
139    pub fn interface_set_admin_state(&self, if_id: u32, up: bool) -> io::Result<()> {
140        let res = self
141            .client
142            .send_request(RtnlLinkRequest::InterfaceSetAdmin { if_id, up })?;
143        let op = if up {
144            "Set interface up"
145        } else {
146            "Set interface down"
147        };
148        handle_status_response(op, res)
149    }
150
151    pub fn interface_set_promiscuous(&self, if_id: u32, enable: bool) -> io::Result<()> {
152        let res = self
153            .client
154            .send_request(RtnlLinkRequest::InterfaceSetPromisc { if_id, enable })?;
155        handle_status_response(
156            if enable {
157                "Enable promiscuous mode"
158            } else {
159                "Disable promiscuous mode"
160            },
161            res,
162        )
163    }
164
165    pub fn interface_set_arp(&self, if_id: u32, enable: bool) -> io::Result<()> {
166        let res = self
167            .client
168            .send_request(RtnlLinkRequest::InterfaceSetArp { if_id, enable })?;
169        handle_status_response(if enable { "Enable ARP" } else { "Disable ARP" }, res)
170    }
171
172    pub fn interface_set_mtu(&self, if_id: u32, mtu: u32) -> io::Result<()> {
173        let res = self
174            .client
175            .send_request(RtnlLinkRequest::InterfaceSetMtu { if_id, mtu })?;
176        handle_status_response("Set MTU", res)
177    }
178
179    pub fn interface_rename(&self, if_id: u32, new_name: &str) -> io::Result<()> {
180        let res = self.client.send_request(RtnlLinkRequest::InterfaceRename {
181            if_id,
182            if_name: new_name.to_owned(),
183        })?;
184        handle_status_response("Rename interface", res)
185    }
186
187    pub fn interface_get(&self, if_id: u32) -> io::Result<Interface> {
188        let res = self
189            .client
190            .send_request(RtnlLinkRequest::InterfaceGet { if_id })?;
191        match res {
192            RtnlLinkResponse::Interface(interface) => Ok(interface),
193            RtnlLinkResponse::NotFound => {
194                Err(io::Error::new(ErrorKind::NotFound, "Interface not found"))
195            }
196            _ => Err(io::Error::other("Failed to get interface")),
197        }
198    }
199
200    pub fn interface_get_by_name(&self, name: &str) -> std::io::Result<Interface> {
201        let name = name.to_owned();
202        let res = self
203            .client
204            .send_request(RtnlLinkRequest::InterfaceGetByName { if_name: name })?;
205        match res {
206            RtnlLinkResponse::Interface(interface) => {
207                return Ok(interface);
208            }
209            _ => {}
210        }
211        Err(std::io::Error::other("Not found"))
212    }
213
214    pub fn mac_addr_get(&self, if_id: u32) -> std::io::Result<Option<MacAddr>> {
215        let res = self
216            .client
217            .send_request(RtnlLinkRequest::MacAddrGet { if_id })?;
218        match res {
219            RtnlLinkResponse::MacAddr(addr) => {
220                return Ok(Some(addr));
221            }
222            _ => {}
223        }
224        Ok(None)
225    }
226
227    pub fn mtu_get(&self, if_id: u32) -> io::Result<u32> {
228        let res = self
229            .client
230            .send_request(RtnlLinkRequest::MtuGet { if_id })?;
231        match res {
232            RtnlLinkResponse::Mtu(mtu) => Ok(mtu),
233            RtnlLinkResponse::NotFound => {
234                Err(io::Error::new(ErrorKind::NotFound, "Interface not found"))
235            }
236            _ => Err(io::Error::other("Failed to get MTU")),
237        }
238    }
239
240    pub fn mac_addr_set(
241        &self,
242        if_id: u32,
243        link_layer_type: LinkLayerType,
244        mac_addr: MacAddr,
245    ) -> io::Result<()> {
246        let res = self.client.send_request(RtnlLinkRequest::MacAddrSet {
247            if_id,
248            link_layer_type,
249            mac_addr,
250        })?;
251        handle_status_response("Set MAC address", res)
252    }
253
254    pub fn interface_set_all_multicast(&self, if_id: u32, enable: bool) -> io::Result<()> {
255        let res = self
256            .client
257            .send_request(RtnlLinkRequest::InterfaceSetAllMulticast { if_id, enable })?;
258        handle_status_response(
259            if enable {
260                "Enable all-multicast"
261            } else {
262                "Disable all-multicast"
263            },
264            res,
265        )
266    }
267
268    pub fn interface_list(&self) -> std::io::Result<Vec<Interface>> {
269        let res = self.client.send_request(RtnlLinkRequest::InterfaceList)?;
270        match res {
271            RtnlLinkResponse::InterfaceList(list) => {
272                return Ok(list);
273            }
274            _ => {}
275        }
276        Err(std::io::Error::other("Unknown error"))
277    }
278}
279
280fn handle_status_response(op: &str, response: RtnlLinkResponse) -> io::Result<()> {
281    match response {
282        RtnlLinkResponse::Success => Ok(()),
283        RtnlLinkResponse::NotFound => Err(io::Error::new(
284            ErrorKind::NotFound,
285            format!("{}: interface not found", op),
286        )),
287        RtnlLinkResponse::Failed => Err(io::Error::other(format!("{} failed", op))),
288        RtnlLinkResponse::FailedWithMessage(msg) => {
289            Err(io::Error::other(format!("{} failed: {}", op, msg)))
290        }
291        RtnlLinkResponse::NotImplemented => Err(io::Error::new(
292            ErrorKind::Unsupported,
293            format!("{} not implemented", op),
294        )),
295        other => Err(io::Error::other(format!(
296            "{} returned unexpected response: {:?}",
297            op, other
298        ))),
299    }
300}
301
302async fn apply_link_set<F>(
303    handle: &rtnetlink::LinkHandle,
304    if_id: u32,
305    link_layer_type: Option<LinkLayerType>,
306    op: F,
307) -> Result<(), rtnetlink::Error>
308where
309    F: FnOnce(LinkMessageBuilder<LinkUnspec>) -> LinkMessageBuilder<LinkUnspec>,
310{
311    let builder = LinkMessageBuilder::<LinkUnspec>::new();
312    let builder = if let Some(link_layer_type) = link_layer_type {
313        let mut header = LinkHeader::default();
314        header.index = if_id;
315        header.link_layer_type = link_layer_type;
316        builder.set_header(header)
317    } else {
318        builder
319    };
320
321    let builder = builder.index(if_id);
322
323    let message = op(builder).build();
324    handle.set(message).execute().await
325}
326
327fn map_link_result(result: Result<(), rtnetlink::Error>, op: &str, if_id: u32) -> RtnlLinkResponse {
328    match result {
329        Ok(()) => RtnlLinkResponse::Success,
330        Err(rtnetlink::Error::NetlinkError(err_msg)) => {
331            let io_err = err_msg.to_io();
332            if io_err.kind() == ErrorKind::NotFound {
333                RtnlLinkResponse::NotFound
334            } else {
335                let message = io_err.to_string();
336                tracing::warn!("Failed to {} for ifindex {}: {}", op, if_id, message);
337                RtnlLinkResponse::FailedWithMessage(message)
338            }
339        }
340        Err(err) => {
341            let message = err.to_string();
342            tracing::warn!("Failed to {} for ifindex {}: {}", op, if_id, message);
343            RtnlLinkResponse::FailedWithMessage(message)
344        }
345    }
346}
347
348pub(crate) async fn run_server(mut server: Server, mut handle: rtnetlink::LinkHandle) {
349    'reqloop: while let Some((req, respond)) = server.accept().await {
350        match req {
351            RtnlLinkRequest::InterfaceGet { if_id } => {
352                if if_id == 0 {
353                    respond(RtnlLinkResponse::NotFound);
354                    continue 'reqloop;
355                }
356
357                let response = handle.get().match_index(if_id).execute();
358                futures::pin_mut!(response);
359                while let Ok(Some(response)) = response.try_next().await {
360                    let mut if_name = None;
361                    for attr in response.attributes.iter() {
362                        if let netlink_packet_route::link::LinkAttribute::IfName(name) = attr {
363                            if_name = Some(name.clone());
364                        }
365                    }
366
367                    if let Some(name) = if_name {
368                        respond(RtnlLinkResponse::Interface(Interface {
369                            if_id,
370                            if_name: name,
371                            link_layer_type: response.header.link_layer_type,
372                        }));
373                        continue 'reqloop;
374                    }
375                }
376                respond(RtnlLinkResponse::NotFound);
377            }
378            RtnlLinkRequest::InterfaceGetByName { if_name } => {
379                let response = handle.get().match_name(if_name.to_owned()).execute();
380                futures::pin_mut!(response);
381                while let Ok(Some(response)) = response.try_next().await {
382                    let if_index = response.header.index;
383                    if if_index == 0 {
384                        continue;
385                    }
386
387                    let name = response
388                        .attributes
389                        .iter()
390                        .find_map(|attr| {
391                            if let netlink_packet_route::link::LinkAttribute::IfName(name) = attr {
392                                Some(name.clone())
393                            } else {
394                                None
395                            }
396                        })
397                        .unwrap_or_else(|| if_name.to_owned());
398
399                    respond(RtnlLinkResponse::Interface(Interface {
400                        if_id: if_index,
401                        if_name: name,
402                        link_layer_type: response.header.link_layer_type,
403                    }));
404                    continue 'reqloop;
405                }
406                respond(RtnlLinkResponse::NotFound);
407            }
408            RtnlLinkRequest::MacAddrGet { if_id } => {
409                let if_index = if_id;
410                if if_index == 0 {
411                    respond(RtnlLinkResponse::NotFound);
412                    continue 'reqloop;
413                }
414                let response = handle.get().match_index(if_index).execute();
415                futures::pin_mut!(response);
416                while let Ok(Some(response)) = response.try_next().await {
417                    for link in response.attributes.iter() {
418                        match link {
419                            netlink_packet_route::link::LinkAttribute::Address(addr) => {
420                                if addr.len() < 6 {
421                                    continue;
422                                }
423                                let mut mac_bytes = [0u8; 6];
424                                mac_bytes.copy_from_slice(&addr[..6]);
425                                respond(RtnlLinkResponse::MacAddr(MacAddr::new(mac_bytes)));
426                                continue 'reqloop;
427                            }
428                            _ => {}
429                        }
430                    }
431                }
432                respond(RtnlLinkResponse::NotFound);
433            }
434            RtnlLinkRequest::MtuGet { if_id } => {
435                if if_id == 0 {
436                    respond(RtnlLinkResponse::NotFound);
437                    continue 'reqloop;
438                }
439
440                let response = handle.get().match_index(if_id).execute();
441                futures::pin_mut!(response);
442                while let Ok(Some(response)) = response.try_next().await {
443                    for link in response.attributes.iter() {
444                        if let netlink_packet_route::link::LinkAttribute::Mtu(mtu) = link {
445                            respond(RtnlLinkResponse::Mtu(*mtu));
446                            continue 'reqloop;
447                        }
448                    }
449                }
450                respond(RtnlLinkResponse::NotFound);
451            }
452            RtnlLinkRequest::InterfaceList => {
453                let mut interfaces = Vec::new();
454                let response = handle.get().execute();
455                futures::pin_mut!(response);
456                while let Ok(Some(response)) = response.try_next().await {
457                    let if_index = response.header.index;
458                    let mut if_name = None;
459                    for link in response.attributes.iter() {
460                        match link {
461                            netlink_packet_route::link::LinkAttribute::IfName(name) => {
462                                if_name = Some(name.clone());
463                            }
464                            _ => {}
465                        }
466                    }
467
468                    if let Some(name) = if_name {
469                        if if_index == 0 {
470                            continue;
471                        }
472
473                        interfaces.push(Interface {
474                            if_id: if_index,
475                            if_name: name,
476                            link_layer_type: response.header.link_layer_type,
477                        });
478                    }
479                }
480                respond(RtnlLinkResponse::InterfaceList(interfaces));
481            }
482            RtnlLinkRequest::MacAddrSet {
483                if_id,
484                link_layer_type,
485                mac_addr,
486            } => {
487                if if_id == 0 {
488                    respond(RtnlLinkResponse::NotFound);
489                    continue 'reqloop;
490                }
491
492                let mac_bytes = mac_addr.inner.to_vec();
493                let result = apply_link_set(&handle, if_id, Some(link_layer_type), |builder| {
494                    builder.address(mac_bytes)
495                })
496                .await;
497                respond(map_link_result(result, "set MAC address", if_id));
498            }
499            RtnlLinkRequest::InterfaceSetAdmin { if_id, up } => {
500                if if_id == 0 {
501                    respond(RtnlLinkResponse::NotFound);
502                    continue 'reqloop;
503                }
504
505                let op_desc = if up {
506                    "set interface up"
507                } else {
508                    "set interface down"
509                };
510                let result = apply_link_set(&handle, if_id, None, |builder| {
511                    if up { builder.up() } else { builder.down() }
512                })
513                .await;
514
515                respond(map_link_result(result, op_desc, if_id));
516            }
517            RtnlLinkRequest::InterfaceSetPromisc { if_id, enable } => {
518                if if_id == 0 {
519                    respond(RtnlLinkResponse::NotFound);
520                    continue 'reqloop;
521                }
522
523                let op_desc = if enable {
524                    "enable promiscuous mode"
525                } else {
526                    "disable promiscuous mode"
527                };
528                let result =
529                    apply_link_set(&handle, if_id, None, |builder| builder.promiscuous(enable))
530                        .await;
531
532                respond(map_link_result(result, op_desc, if_id));
533            }
534            RtnlLinkRequest::InterfaceSetArp { if_id, enable } => {
535                if if_id == 0 {
536                    respond(RtnlLinkResponse::NotFound);
537                    continue 'reqloop;
538                }
539
540                let op_desc = if enable { "enable ARP" } else { "disable ARP" };
541                let result =
542                    apply_link_set(&handle, if_id, None, |builder| builder.arp(enable)).await;
543
544                respond(map_link_result(result, op_desc, if_id));
545            }
546            RtnlLinkRequest::InterfaceSetMtu { if_id, mtu } => {
547                if if_id == 0 {
548                    respond(RtnlLinkResponse::NotFound);
549                    continue 'reqloop;
550                }
551
552                let result = apply_link_set(&handle, if_id, None, |builder| builder.mtu(mtu)).await;
553                respond(map_link_result(result, "set MTU", if_id));
554            }
555            RtnlLinkRequest::InterfaceRename { if_id, if_name } => {
556                if if_id == 0 {
557                    respond(RtnlLinkResponse::NotFound);
558                    continue 'reqloop;
559                }
560
561                let new_name = if_name.clone();
562                let result =
563                    apply_link_set(&handle, if_id, None, |builder| builder.name(new_name)).await;
564                let op_desc = format!("rename interface to {}", if_name);
565                respond(map_link_result(result, &op_desc, if_id));
566            }
567            RtnlLinkRequest::InterfaceSetAllMulticast { if_id, enable } => {
568                if if_id == 0 {
569                    respond(RtnlLinkResponse::NotFound);
570                    continue 'reqloop;
571                }
572
573                let op_desc = if enable {
574                    "enable all-multicast mode"
575                } else {
576                    "disable all-multicast mode"
577                };
578
579                let mut message = LinkMessageBuilder::<LinkUnspec>::new().index(if_id).build();
580                if enable {
581                    message.header.flags |= LinkFlags::Allmulti;
582                } else {
583                    message.header.flags.remove(LinkFlags::Allmulti);
584                }
585                message.header.change_mask |= LinkFlags::Allmulti;
586
587                let result = handle.set(message).execute().await;
588
589                respond(map_link_result(result, op_desc, if_id));
590            }
591            _ => respond(RtnlLinkResponse::NotImplemented),
592        }
593    }
594}