ipmi_rs/connection/impls/
file.rs1use 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 unsafe {
294 ioctl::ipmi_send_request(self.fd(), std::ptr::addr_of_mut!(request))?;
295 }
296
297 #[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 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 #[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}