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}