ndn_faces/
iface_watcher.rs1#[derive(Debug, Clone)]
15pub enum InterfaceEvent {
16 Added(String),
18 Removed(String),
20}
21
22pub async fn watch_interfaces(
29 tx: tokio::sync::mpsc::Sender<InterfaceEvent>,
30 cancel: tokio_util::sync::CancellationToken,
31) {
32 #[cfg(target_os = "linux")]
33 {
34 watch_interfaces_linux(tx, cancel).await;
35 }
36 #[cfg(not(target_os = "linux"))]
37 {
38 let _ = (tx, cancel);
39 tracing::warn!(
40 "`watch_interfaces` is only supported on Linux; \
41 interface hotplug disabled on this platform"
42 );
43 }
44}
45
46#[cfg(target_os = "linux")]
49async fn watch_interfaces_linux(
50 tx: tokio::sync::mpsc::Sender<InterfaceEvent>,
51 cancel: tokio_util::sync::CancellationToken,
52) {
53 use std::os::unix::io::OwnedFd;
54 use tokio::io::unix::AsyncFd;
55
56 const RTM_NEWLINK: u16 = 16;
58
59 let fd: i32 = unsafe {
61 libc::socket(
62 libc::AF_NETLINK,
63 libc::SOCK_RAW | libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK,
64 libc::NETLINK_ROUTE,
65 )
66 };
67 if fd < 0 {
68 tracing::warn!(
69 error = %std::io::Error::last_os_error(),
70 "failed to open netlink socket for interface watching"
71 );
72 return;
73 }
74
75 let mut addr: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
79 addr.nl_family = libc::AF_NETLINK as u16;
80 addr.nl_groups = 1;
83 let rc = unsafe {
84 libc::bind(
85 fd,
86 &addr as *const libc::sockaddr_nl as *const libc::sockaddr,
87 std::mem::size_of::<libc::sockaddr_nl>() as u32,
88 )
89 };
90 if rc != 0 {
91 tracing::warn!(
92 error = %std::io::Error::last_os_error(),
93 "failed to bind netlink socket — interface hotplug disabled"
94 );
95 unsafe {
96 libc::close(fd);
97 }
98 return;
99 }
100
101 let owned: OwnedFd = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
103 let async_fd = match AsyncFd::new(owned) {
104 Ok(f) => f,
105 Err(e) => {
106 tracing::warn!(error=%e, "failed to register netlink fd with tokio");
107 return;
108 }
109 };
110
111 tracing::info!("interface watcher active (netlink RTMGRP_LINK)");
112
113 let mut buf = vec![0u8; 8192];
114
115 loop {
116 tokio::select! {
117 _ = cancel.cancelled() => break,
118 result = async_fd.readable() => {
119 let mut guard = match result {
120 Ok(g) => g,
121 Err(e) => {
122 tracing::warn!(error=%e, "netlink read error");
123 break;
124 }
125 };
126 let n = unsafe {
127 libc::recv(
128 async_fd.as_raw_fd(),
129 buf.as_mut_ptr() as *mut libc::c_void,
130 buf.len(),
131 0,
132 )
133 };
134 guard.clear_ready();
135 if n <= 0 {
136 continue;
137 }
138 let msgs = parse_rtm_link_messages(&buf[..n as usize]);
140 for (msg_type, iface_name) in msgs {
141 let event = if msg_type == RTM_NEWLINK {
142 InterfaceEvent::Added(iface_name.clone())
143 } else {
144 InterfaceEvent::Removed(iface_name.clone())
145 };
146 tracing::debug!(
147 iface = %iface_name,
148 event = if msg_type == RTM_NEWLINK { "added" } else { "removed" },
149 "interface event"
150 );
151 if tx.send(event).await.is_err() {
152 return; }
154 }
155 }
156 }
157 }
158}
159
160#[cfg(target_os = "linux")]
161use std::os::unix::io::AsRawFd;
162
163#[cfg(target_os = "linux")]
167fn parse_rtm_link_messages(buf: &[u8]) -> Vec<(u16, String)> {
168 const NLMSG_HDR: usize = 16;
171 const IFINFO_HDR: usize = 16;
172 const RTA_HDR: usize = 4;
173 const IFLA_IFNAME: u16 = 3;
174 const RTM_NEWLINK: u16 = 16;
175 const RTM_DELLINK: u16 = 17;
176
177 let mut results = Vec::new();
178 let mut offset = 0usize;
179
180 while offset + NLMSG_HDR <= buf.len() {
181 let nlmsg_len = u32::from_ne_bytes(buf[offset..offset + 4].try_into().unwrap()) as usize;
183 let nlmsg_type = u16::from_ne_bytes(buf[offset + 4..offset + 6].try_into().unwrap());
184
185 if nlmsg_len < NLMSG_HDR || offset + nlmsg_len > buf.len() {
186 break;
187 }
188
189 if nlmsg_type == RTM_NEWLINK || nlmsg_type == RTM_DELLINK {
190 let attr_start = offset + NLMSG_HDR + IFINFO_HDR;
192 let attr_end = offset + nlmsg_len;
193 let mut attr_off = attr_start;
194
195 while attr_off + RTA_HDR <= attr_end {
196 let rta_len =
197 u16::from_ne_bytes(buf[attr_off..attr_off + 2].try_into().unwrap()) as usize;
198 let rta_type =
199 u16::from_ne_bytes(buf[attr_off + 2..attr_off + 4].try_into().unwrap());
200 if rta_len < RTA_HDR || attr_off + rta_len > attr_end {
201 break;
202 }
203 if rta_type == IFLA_IFNAME {
204 let data = &buf[attr_off + RTA_HDR..attr_off + rta_len];
205 let name = data.split(|&b| b == 0).next().unwrap_or(data);
207 if let Ok(s) = std::str::from_utf8(name) {
208 results.push((nlmsg_type, s.to_owned()));
209 }
210 }
211 let aligned = (rta_len + 3) & !3;
213 attr_off += aligned;
214 }
215 }
216
217 let aligned = (nlmsg_len + 3) & !3;
219 offset += aligned;
220 }
221
222 results
223}