1use std::alloc;
16use std::ffi::CStr;
17use std::mem;
18use std::os::fd::AsRawFd;
19use std::os::fd::OwnedFd;
20use std::ptr;
21use std::str;
22
23use nix::errno::Errno;
24use nix::libc;
25use nix::sys::socket::socket;
26use nix::sys::socket::AddressFamily;
27use nix::sys::socket::SockFlag;
28use nix::sys::socket::SockType;
29
30use crate::errors::EthtoolError;
31use crate::ethtool_sys;
32const ETH_GSTATS_LEN: usize = 8;
33
34fn if_name_bytes(if_name: &str) -> [libc::c_char; libc::IF_NAMESIZE] {
35 let mut it = if_name.as_bytes().iter().copied();
36 [0; libc::IF_NAMESIZE].map(|_| it.next().unwrap_or(0) as libc::c_char)
37}
38
39fn ioctl(
40 fd: &OwnedFd,
41 if_name: [libc::c_char; libc::IF_NAMESIZE],
42 data: *mut libc::c_char,
43) -> Result<(), Errno> {
44 let ifr = libc::ifreq {
45 ifr_name: if_name,
46 ifr_ifru: libc::__c_anonymous_ifr_ifru { ifru_data: data },
47 };
48
49 #[allow(clippy::useless_conversion)]
58 let exit_code = unsafe { libc::ioctl(fd.as_raw_fd(), libc::SIOCETHTOOL as _, &ifr) };
59 if exit_code != 0 {
60 return Err(Errno::from_i32(exit_code));
61 }
62 Ok(())
63}
64
65fn parse_names(data: &[u8]) -> Result<Vec<String>, EthtoolError> {
69 let names = data
70 .chunks(ethtool_sys::ETH_GSTRING_LEN as usize)
71 .map(|chunk| {
72 let c_str = CStr::from_bytes_until_nul(chunk);
74 match c_str {
75 Ok(c_str) => Ok(c_str.to_string_lossy().into_owned()),
76 Err(err) => Err(EthtoolError::ParseError(err.to_string())),
77 }
78 })
79 .collect::<Result<Vec<String>, EthtoolError>>()?;
80
81 Ok(names)
82}
83
84fn parse_values(data: &[u64], length: usize) -> Result<Vec<u64>, EthtoolError> {
88 let values = data.iter().take(length).copied().collect::<Vec<u64>>();
89
90 Ok(values)
91}
92
93struct StringSetInfo {
94 layout: alloc::Layout,
95 ptr: *mut ethtool_sys::ethtool_sset_info,
96}
97
98impl StringSetInfo {
99 fn new() -> Result<Self, EthtoolError> {
101 let layout = alloc::Layout::from_size_align(
103 mem::size_of::<ethtool_sys::ethtool_sset_info>(),
104 mem::align_of::<ethtool_sys::ethtool_sset_info>(),
105 )
106 .map_err(EthtoolError::CStructInitError)?;
107
108 let sset_info_ptr = unsafe { alloc::alloc(layout) } as *mut ethtool_sys::ethtool_sset_info;
110
111 unsafe {
113 let cmd_ptr = ptr::addr_of_mut!((*sset_info_ptr).cmd);
114 let reserved_ptr = ptr::addr_of_mut!((*sset_info_ptr).reserved);
115 let sset_mask_ptr = ptr::addr_of_mut!((*sset_info_ptr).sset_mask);
116 let data_ptr = ptr::addr_of_mut!((*sset_info_ptr).data);
117
118 cmd_ptr.write(ethtool_sys::ETHTOOL_GSSET_INFO);
119 reserved_ptr.write(1u32);
120 sset_mask_ptr.write((1 << ethtool_sys::ethtool_stringset_ETH_SS_STATS) as u64);
121
122 data_ptr.write_bytes(0u8, mem::size_of::<u32>());
123 }
124
125 Ok(StringSetInfo {
126 layout,
127 ptr: sset_info_ptr,
128 })
129 }
130
131 fn data(&self) -> Result<usize, EthtoolError> {
132 unsafe {
133 let value = self.ptr.as_ref().ok_or(EthtoolError::CStructReadError())?;
134 Ok(value.data.as_ptr().read() as usize)
135 }
136 }
137}
138
139impl Drop for StringSetInfo {
140 fn drop(&mut self) {
141 unsafe {
142 alloc::dealloc(self.ptr as *mut u8, self.layout);
143 }
144 }
145}
146
147struct GStrings {
148 length: usize,
149 layout: alloc::Layout,
150 ptr: *mut ethtool_sys::ethtool_gstrings,
151}
152
153impl GStrings {
154 fn new(length: usize) -> Result<Self, EthtoolError> {
155 let data_length = length * ethtool_sys::ETH_GSTRING_LEN as usize;
156
157 let layout = alloc::Layout::from_size_align(
159 mem::size_of::<ethtool_sys::ethtool_gstrings>() + data_length * mem::size_of::<u8>(),
160 mem::align_of::<ethtool_sys::ethtool_gstrings>(),
161 )
162 .map_err(EthtoolError::CStructInitError)?;
163
164 let gstrings_ptr = unsafe { alloc::alloc(layout) } as *mut ethtool_sys::ethtool_gstrings;
166 if gstrings_ptr.is_null() {
167 return Err(EthtoolError::AllocationFailure());
168 }
169
170 unsafe {
172 let cmd_ptr = ptr::addr_of_mut!((*gstrings_ptr).cmd);
173 let ss_ptr = ptr::addr_of_mut!((*gstrings_ptr).string_set);
174 let data_ptr = ptr::addr_of_mut!((*gstrings_ptr).data);
175
176 cmd_ptr.write(ethtool_sys::ETHTOOL_GSTRINGS);
177 ss_ptr.write(ethtool_sys::ethtool_stringset_ETH_SS_STATS);
178
179 data_ptr.write_bytes(0u8, data_length);
181 }
182
183 Ok(GStrings {
184 length,
185 layout,
186 ptr: gstrings_ptr,
187 })
188 }
189
190 fn data(&self) -> Result<&[u8], EthtoolError> {
191 unsafe {
192 Ok(std::slice::from_raw_parts(
193 (*self.ptr).data.as_ptr(),
194 self.length * ethtool_sys::ETH_GSTRING_LEN as usize,
195 ))
196 }
197 }
198}
199
200impl Drop for GStrings {
201 fn drop(&mut self) {
202 unsafe {
203 alloc::dealloc(self.ptr as *mut u8, self.layout);
204 }
205 }
206}
207
208struct GStats {
209 length: usize,
210 layout: alloc::Layout,
211 ptr: *mut ethtool_sys::ethtool_stats,
212}
213
214impl GStats {
215 fn new(length: usize) -> Result<Self, EthtoolError> {
217 let data_length = length * ETH_GSTATS_LEN;
218
219 let layout = alloc::Layout::from_size_align(
221 mem::size_of::<ethtool_sys::ethtool_stats>() + data_length * mem::size_of::<u64>(),
222 mem::align_of::<ethtool_sys::ethtool_stats>(),
223 )
224 .map_err(EthtoolError::CStructInitError)?;
225
226 let stats_ptr = unsafe { alloc::alloc(layout) } as *mut ethtool_sys::ethtool_stats;
228 if stats_ptr.is_null() {
229 return Err(EthtoolError::AllocationFailure());
230 }
231
232 unsafe {
234 let cmd_ptr = ptr::addr_of_mut!((*stats_ptr).cmd);
235 let data_ptr = ptr::addr_of_mut!((*stats_ptr).data);
236
237 cmd_ptr.write(ethtool_sys::ETHTOOL_GSTATS);
238
239 let data_bytes = data_length * mem::size_of::<u64>();
241 data_ptr.write_bytes(0u8, data_bytes);
242 }
243
244 Ok(GStats {
245 length,
246 layout,
247 ptr: stats_ptr,
248 })
249 }
250
251 fn data(&self) -> Result<&[u64], EthtoolError> {
252 unsafe {
253 Ok(std::slice::from_raw_parts(
254 (*self.ptr).data.as_ptr(),
255 self.length * ETH_GSTATS_LEN,
256 ))
257 }
258 }
259}
260
261impl Drop for GStats {
262 fn drop(&mut self) {
263 unsafe {
264 alloc::dealloc(self.ptr as *mut u8, self.layout);
265 }
266 }
267}
268
269pub trait EthtoolReadable {
273 fn new(if_name: &str) -> Result<Self, EthtoolError>
274 where
275 Self: Sized;
276 fn stats(&self) -> Result<Vec<(String, u64)>, EthtoolError>;
277}
278
279pub struct Ethtool {
280 sock_fd: OwnedFd,
281 if_name: [libc::c_char; libc::IF_NAMESIZE],
282}
283
284impl Ethtool {
285 fn gsset_info(&self) -> Result<usize, EthtoolError> {
287 let sset_info = StringSetInfo::new()?;
288 let data = sset_info.ptr as *mut libc::c_char;
289
290 match ioctl(&self.sock_fd, self.if_name, data) {
291 Ok(_) => Ok(sset_info.data()?),
292 Err(errno) => Err(EthtoolError::SocketError(errno)),
293 }
294 }
295
296 fn gstrings(&self, length: usize) -> Result<Vec<String>, EthtoolError> {
298 let gstrings = GStrings::new(length)?;
299 let data = gstrings.ptr as *mut libc::c_char;
300
301 match ioctl(&self.sock_fd, self.if_name, data) {
302 Ok(_) => parse_names(gstrings.data()?),
303 Err(errno) => Err(EthtoolError::GStringsReadError(errno)),
304 }
305 }
306
307 fn gstats(&self, features: &[String]) -> Result<Vec<u64>, EthtoolError> {
309 let length = features.len();
310 let gstats = GStats::new(length)?;
311 let data = gstats.ptr as *mut libc::c_char;
312
313 match ioctl(&self.sock_fd, self.if_name, data) {
314 Ok(_) => parse_values(gstats.data()?, length),
315 Err(errno) => Err(EthtoolError::GStatsReadError(errno)),
316 }
317 }
318}
319
320impl EthtoolReadable for Ethtool {
321 fn new(if_name: &str) -> Result<Self, EthtoolError> {
322 match socket(
323 AddressFamily::Inet,
324 SockType::Datagram,
325 SockFlag::empty(),
326 None,
327 ) {
328 Ok(sock_fd) => Ok(Ethtool {
329 sock_fd,
330 if_name: if_name_bytes(if_name),
331 }),
332 Err(errno) => Err(EthtoolError::SocketError(errno)),
333 }
334 }
335
336 fn stats(&self) -> Result<Vec<(String, u64)>, EthtoolError> {
339 let length = self.gsset_info()?;
340
341 let features = self.gstrings(length)?;
342 let values = self.gstats(&features)?;
343
344 let final_stats = features.into_iter().zip(values).collect();
345 Ok(final_stats)
346 }
347}