ipmi_rs/connection/impls/
file.rs

1use std::fmt::{Display, Formatter};
2use std::{
3    ffi::c_int,
4    io,
5    os::fd::AsRawFd,
6    time::{Duration, Instant},
7};
8
9use crate::{
10    connection::{Address, IpmiConnection, Message, Request, RequestTargetAddress, Response},
11    NetFn,
12};
13
14#[repr(C)]
15#[derive(Debug)]
16pub struct IpmiMessage {
17    netfn: u8,
18    cmd: u8,
19    data_len: u16,
20    data: *mut u8,
21}
22
23impl IpmiMessage {
24    fn data(&self) -> &[u8] {
25        unsafe { core::slice::from_raw_parts(self.data, self.data_len as usize) }
26    }
27}
28
29impl IpmiMessage {
30    fn log(&self, level: log::Level) {
31        log::log!(level, "  NetFn      = 0x{:02X}", self.netfn);
32        log::log!(level, "  Command    = 0x{:02X}", self.cmd);
33        log::log!(level, "  Data len   = {}", self.data_len);
34        if self.data_len > 0 {
35            log::log!(level, "  Data       = {:02X?}", self.data());
36        }
37    }
38}
39
40#[repr(C)]
41#[derive(Debug)]
42pub struct IpmiRequest {
43    addr: *mut u8,
44    addr_len: u32,
45    msg_id: i64,
46    message: IpmiMessage,
47}
48
49impl IpmiRequest {
50    pub fn log(&self, level: log::Level) {
51        log::log!(level, "  Message ID = 0x{:02X}", self.msg_id);
52        self.message.log(level)
53    }
54}
55
56#[repr(C)]
57#[derive(Debug)]
58pub struct IpmiRecv {
59    recv_type: i32,
60    addr: *mut u8,
61    addr_len: u32,
62    msg_id: i64,
63    message: IpmiMessage,
64}
65
66impl IpmiRecv {
67    fn log(&self, level: log::Level) {
68        log::log!(level, "  Type       = 0x{:02X}", self.recv_type);
69        log::log!(level, "  Message ID = 0x{:02X}", self.msg_id);
70        self.message.log(level)
71    }
72}
73
74#[derive(Clone, Copy, Debug)]
75pub enum CreateResponseError {
76    NotAResponse,
77    NotEnoughData,
78    InvalidCmd,
79}
80
81impl TryFrom<IpmiRecv> for Response {
82    type Error = CreateResponseError;
83
84    fn try_from(value: IpmiRecv) -> Result<Self, Self::Error> {
85        let (netfn, cmd) = (value.message.netfn, value.message.cmd);
86
87        let netfn_parsed = NetFn::from(netfn);
88
89        if netfn_parsed.response_value() == netfn {
90            let message = Message::new_raw(netfn, cmd, value.message.data().to_vec());
91            let response =
92                Response::new(message, value.msg_id).ok_or(CreateResponseError::NotEnoughData)?;
93            Ok(response)
94        } else {
95            Err(CreateResponseError::NotAResponse)
96        }
97    }
98}
99
100mod ioctl {
101    const IPMI_IOC_MAGIC: u8 = b'i';
102
103    use nix::{ioctl_read, ioctl_readwrite};
104
105    use super::*;
106
107    ioctl_readwrite!(ipmi_recv_msg_trunc, IPMI_IOC_MAGIC, 11, IpmiRecv);
108    ioctl_read!(ipmi_send_request, IPMI_IOC_MAGIC, 13, IpmiRequest);
109    ioctl_read!(ipmi_get_my_address, IPMI_IOC_MAGIC, 18, u32);
110}
111
112#[repr(C)]
113enum IpmiAddr {
114    SysIface(IpmiSysIfaceAddr),
115    Ipmb(IpmiIpmbAddr),
116}
117
118impl IpmiAddr {
119    fn ptr(&mut self) -> *mut u8 {
120        match self {
121            IpmiAddr::SysIface(ref mut bmc_addr) => std::ptr::addr_of_mut!(*bmc_addr) as *mut u8,
122            IpmiAddr::Ipmb(ref mut ipmb_addr) => std::ptr::addr_of_mut!(*ipmb_addr) as *mut u8,
123        }
124    }
125    fn size(&self) -> u32 {
126        match self {
127            IpmiAddr::SysIface(_) => core::mem::size_of::<IpmiSysIfaceAddr>() as u32,
128            IpmiAddr::Ipmb(_) => core::mem::size_of::<IpmiIpmbAddr>() as u32,
129        }
130    }
131}
132
133impl From<RequestTargetAddress> for IpmiAddr {
134    fn from(value: RequestTargetAddress) -> Self {
135        match value {
136            RequestTargetAddress::Bmc(lun) => {
137                IpmiAddr::SysIface(IpmiSysIfaceAddr::bmc(lun.value()))
138            }
139            RequestTargetAddress::BmcOrIpmb(addr, channel, lun) => IpmiAddr::Ipmb(
140                IpmiIpmbAddr::new(channel.value() as i16, addr.0, lun.value()),
141            ),
142        }
143    }
144}
145
146impl Display for IpmiAddr {
147    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148        match self {
149            IpmiAddr::SysIface(addr) => {
150                write!(f, "System interface (LUN: {})", addr.lun)
151            }
152            IpmiAddr::Ipmb(addr) => {
153                write!(
154                    f,
155                    "IPMB target (Channel: {}, Target: {}, LUN: {})",
156                    addr.channel, addr.target_addr, addr.lun
157                )
158            }
159        }
160    }
161}
162
163#[repr(C)]
164#[derive(Clone, Debug, PartialEq)]
165pub struct IpmiSysIfaceAddr {
166    ty: i32,
167    channel: i16,
168    lun: u8,
169}
170
171#[repr(C)]
172#[derive(Clone, Debug, PartialEq)]
173pub struct IpmiIpmbAddr {
174    ty: i32,
175    channel: i16,
176    target_addr: u8,
177    lun: u8,
178}
179
180impl IpmiIpmbAddr {
181    const IPMI_IPMB_ADDR_TYPE: i32 = 0x01;
182
183    pub const fn new(channel: i16, target_addr: u8, lun: u8) -> Self {
184        Self {
185            ty: Self::IPMI_IPMB_ADDR_TYPE,
186            channel,
187            target_addr,
188            lun,
189        }
190    }
191}
192
193impl IpmiSysIfaceAddr {
194    const IPMI_SYSTEM_INTERFACE_ADDR_TYPE: i32 = 0x0c;
195    const IPMI_BMC_CHANNEL: i16 = 0xf;
196
197    pub const fn bmc(lun: u8) -> Self {
198        Self {
199            ty: Self::IPMI_SYSTEM_INTERFACE_ADDR_TYPE,
200            channel: Self::IPMI_BMC_CHANNEL,
201            lun,
202        }
203    }
204}
205
206pub struct File {
207    inner: std::fs::File,
208    recv_timeout: Duration,
209    seq: i64,
210    my_addr: Address,
211}
212
213impl File {
214    fn fd(&mut self) -> c_int {
215        self.inner.as_raw_fd()
216    }
217
218    pub fn new(path: impl AsRef<std::path::Path>, recv_timeout: Duration) -> io::Result<Self> {
219        let mut inner = std::fs::File::open(path)?;
220
221        let my_addr = match Self::load_my_address_from_file(&mut inner) {
222            Ok(addr) => addr,
223            Err(e) => {
224                log::warn!("Failed to get local address, defaulting to 0x20: {:?}", e);
225                Address(0x20)
226            }
227        };
228
229        Ok(Self {
230            inner,
231            recv_timeout,
232            seq: -1,
233            my_addr,
234        })
235    }
236
237    fn load_my_address_from_file(file: &mut std::fs::File) -> io::Result<Address> {
238        let mut my_addr: u32 = 8;
239        unsafe { ioctl::ipmi_get_my_address(file.as_raw_fd(), std::ptr::addr_of_mut!(my_addr))? };
240        if let Ok(addr) = u8::try_from(my_addr) {
241            Ok(Address(addr))
242        } else {
243            Err(io::Error::new(
244                io::ErrorKind::Other,
245                format!("ipmi_get_my_address returned non-u8 address: {}", my_addr),
246            ))
247        }
248    }
249}
250
251impl IpmiConnection for File {
252    type SendError = io::Error;
253    type RecvError = io::Error;
254    type Error = io::Error;
255
256    fn send(&mut self, request: &mut Request) -> io::Result<()> {
257        let mut addr: IpmiAddr = match request.target() {
258            RequestTargetAddress::BmcOrIpmb(a, _, lun) if a == self.my_addr => {
259                RequestTargetAddress::Bmc(lun)
260            }
261            x => x,
262        }
263        .into();
264
265        self.seq += 1;
266
267        let netfn = request.netfn_raw();
268        let cmd = request.cmd();
269        let seq = self.seq;
270        let data = request.data_mut();
271
272        let data_len = data.len() as u16;
273        let ptr = data.as_mut_ptr();
274
275        log::debug!("Sending request (netfn: 0x{netfn:02X}, cmd: 0x{cmd:02X}) to {addr}");
276        let ipmi_message = IpmiMessage {
277            netfn,
278            cmd,
279            data_len,
280            data: ptr,
281        };
282        let mut request = IpmiRequest {
283            addr: addr.ptr(),
284            addr_len: addr.size(),
285            msg_id: seq,
286            message: ipmi_message,
287        };
288
289        request.log(log::Level::Trace);
290
291        // SAFETY: we send a mut pointer to an owned struct (`request`),
292        // which has the correct layout for this IOCTL call.
293        unsafe {
294            ioctl::ipmi_send_request(self.fd(), std::ptr::addr_of_mut!(request))?;
295        }
296
297        // Ensure that data and bmc_addr live until _after_ the IOCTL completes.
298        #[allow(clippy::drop_non_drop)]
299        drop(request);
300        #[allow(clippy::drop_non_drop)]
301        drop(addr);
302
303        Ok(())
304    }
305
306    fn recv(&mut self) -> io::Result<Response> {
307        let start = std::time::Instant::now();
308
309        let mut bmc_addr = IpmiSysIfaceAddr::bmc(0);
310
311        let mut response_data = [0u8; 1024];
312
313        let response_data_len = response_data.len() as u16;
314        let response_data_ptr = response_data.as_mut_ptr();
315
316        let mut recv = IpmiRecv {
317            addr: std::ptr::addr_of_mut!(bmc_addr) as *mut u8,
318            addr_len: core::mem::size_of::<IpmiSysIfaceAddr>() as u32,
319            msg_id: 0,
320            recv_type: 0,
321            message: IpmiMessage {
322                netfn: 0,
323                cmd: 0,
324                data_len: response_data_len,
325                data: response_data_ptr,
326            },
327        };
328
329        let start_time = Instant::now();
330
331        let ipmi_result = loop {
332            // SAFETY: we send a mut pointer to a fully owned struct (`recv`),
333            // which has the correct layout for this IOCTL call.
334            let ioctl_result =
335                unsafe { ioctl::ipmi_recv_msg_trunc(self.fd(), std::ptr::addr_of_mut!(recv)) };
336
337            match ioctl_result {
338                Ok(_) => break Ok(recv),
339                Err(e) => {
340                    if Instant::now().duration_since(start_time) > self.recv_timeout {
341                        break Err(e);
342                    } else {
343                        continue;
344                    }
345                }
346            }
347        };
348
349        // Ensure that response_data and bmc_addr live until _after_ the
350        // IOCTL completes.
351        #[allow(dropping_copy_types)]
352        drop(response_data);
353        #[allow(clippy::drop_non_drop)]
354        drop(bmc_addr);
355
356        let end = std::time::Instant::now();
357        let duration = (end - start).as_millis() as u32;
358
359        match ipmi_result {
360            Ok(recv) => {
361                log::debug!("Received response after {} ms", duration);
362                recv.log(log::Level::Trace);
363
364                match Response::try_from(recv) {
365                    Ok(response) => {
366                        if response.seq() == self.seq {
367                            Ok(response)
368                        } else {
369                            Err(io::Error::new(
370                                io::ErrorKind::Other,
371                                format!(
372                                    "Invalid sequence number on response. Expected {}, got {}",
373                                    self.seq,
374                                    response.seq()
375                                ),
376                            ))
377                        }
378                    }
379                    Err(e) => Err(io::Error::new(
380                        io::ErrorKind::Other,
381                        format!("Error while creating response. {:?}", e),
382                    )),
383                }
384            }
385            Err(e) => {
386                log::warn!(
387                    "Failed to receive message after waiting for {} ms. {:?}",
388                    e,
389                    duration
390                );
391                Err(e.into())
392            }
393        }
394    }
395
396    fn send_recv(&mut self, request: &mut Request) -> io::Result<Response> {
397        self.send(request)?;
398
399        self.recv()
400    }
401}