mctp_linux/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2/*
3 * MCTP support through Linux kernel-based sockets
4 *
5 * Copyright (c) 2024 Code Construct
6 */
7
8#![warn(missing_docs)]
9
10//! Interface for the Linux socket-based MCTP support.
11//!
12//! This crate provides some minimal wrappers around standard [`libc`] socket
13//! operations, using MCTP-specific addressing structures.
14//!
15//! [`MctpSocket`] provides support for blocking socket operations
16//! [sendto](MctpSocket::sendto), [`recvfrom`](MctpSocket::recvfrom) and
17//! [`bind`](MctpSocket::bind).
18//!
19//! ```no_run
20//! use mctp_linux;
21//!
22//! let sock = mctp_linux::MctpSocket::new()?;
23//! let bind_addr = mctp_linux::MctpSockAddr::new(
24//!     mctp::MCTP_ADDR_ANY.0,
25//!     mctp_linux::MCTP_NET_ANY,
26//!     1,
27//!     0
28//! );
29//! sock.bind(&bind_addr)?;
30//!
31//! let mut buf = [0; 64];
32//! let (len, src) = sock.recvfrom(&mut buf)?;
33//! # Ok::<(), std::io::Error>(())
34//! ```
35//!
36//! [`MctpLinuxReq`] provides a thin wrapper that represents a remote endpoint,
37//! referenced by EID. It creates a single socket for communication with that
38//! endpoint. This is a convenience for simple consumers that perform
39//! single-endpoint communication; general MCTP requesters may want a different
40//! socket model.
41
42use core::mem;
43use std::fmt;
44use std::io::Error;
45use std::os::unix::io::RawFd;
46use std::time::Duration;
47
48use mctp::{
49    Eid, MsgIC, MsgType, Result, Tag, TagValue, MCTP_ADDR_ANY, MCTP_TAG_OWNER,
50};
51
52/* until we have these in libc... */
53const AF_MCTP: libc::sa_family_t = 45;
54#[repr(C)]
55#[allow(non_camel_case_types)]
56struct sockaddr_mctp {
57    smctp_family: libc::sa_family_t,
58    __smctp_pad0: u16,
59    smctp_network: u32,
60    smctp_addr: u8,
61    smctp_type: u8,
62    smctp_tag: u8,
63    __smctp_pad1: u8,
64}
65
66/// Special value for Network ID: any network
67pub const MCTP_NET_ANY: u32 = 0x00;
68
69/// Address information for a socket
70pub struct MctpSockAddr(sockaddr_mctp);
71
72impl MctpSockAddr {
73    /// Create a new address, for the given local EID, network, message type,
74    /// and tag value.
75    pub fn new(eid: u8, net: u32, typ: u8, tag: u8) -> Self {
76        MctpSockAddr(sockaddr_mctp {
77            smctp_family: AF_MCTP,
78            __smctp_pad0: 0,
79            smctp_network: net,
80            smctp_addr: eid,
81            smctp_type: typ,
82            smctp_tag: tag,
83            __smctp_pad1: 0,
84        })
85    }
86
87    fn zero() -> Self {
88        Self::new(0, MCTP_NET_ANY, 0, 0)
89    }
90
91    fn as_raw(&self) -> (*const libc::sockaddr, libc::socklen_t) {
92        (
93            &self.0 as *const sockaddr_mctp as *const libc::sockaddr,
94            mem::size_of::<sockaddr_mctp>() as libc::socklen_t,
95        )
96    }
97
98    fn as_raw_mut(&mut self) -> (*mut libc::sockaddr, libc::socklen_t) {
99        (
100            &mut self.0 as *mut sockaddr_mctp as *mut libc::sockaddr,
101            mem::size_of::<sockaddr_mctp>() as libc::socklen_t,
102        )
103    }
104}
105
106impl fmt::Debug for MctpSockAddr {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(
109            f,
110            "McptSockAddr(family={}, net={}, addr={}, type={}, tag={})",
111            self.0.smctp_family,
112            self.0.smctp_network,
113            self.0.smctp_addr,
114            self.0.smctp_type,
115            self.0.smctp_tag
116        )
117    }
118}
119
120/// Creates a `Tag` from a smctp_tag field.
121fn tag_from_smctp(to: u8) -> Tag {
122    let t = TagValue(to & !MCTP_TAG_OWNER);
123    if to & MCTP_TAG_OWNER == 0 {
124        Tag::Unowned(t)
125    } else {
126        Tag::Owned(t)
127    }
128}
129
130/// Creates a `Tag` to a smctp_tag field.
131fn tag_to_smctp(tag: &Tag) -> u8 {
132    let to_bit = if tag.is_owner() { MCTP_TAG_OWNER } else { 0 };
133    tag.tag().0 | to_bit
134}
135
136// helper for IO error construction
137fn last_os_error() -> mctp::Error {
138    mctp::Error::Io(Error::last_os_error())
139}
140
141/// MCTP socket object.
142pub struct MctpSocket(RawFd);
143
144impl Drop for MctpSocket {
145    fn drop(&mut self) {
146        unsafe { libc::close(self.0) };
147    }
148}
149
150impl MctpSocket {
151    /// Create a new MCTP socket. This can then be used for send/receive
152    /// operations.
153    pub fn new() -> Result<Self> {
154        let rc = unsafe {
155            libc::socket(
156                AF_MCTP.into(),
157                libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
158                0,
159            )
160        };
161        if rc < 0 {
162            return Err(last_os_error());
163        }
164        Ok(MctpSocket(rc))
165    }
166
167    /// Blocking receive from a socket, into `buf`, returning a length
168    /// and source address
169    ///
170    /// Essentially a wrapper around [libc::recvfrom], using MCTP-specific
171    /// addressing.
172    pub fn recvfrom(&self, buf: &mut [u8]) -> Result<(usize, MctpSockAddr)> {
173        let mut addr = MctpSockAddr::zero();
174        let (addr_ptr, mut addr_len) = addr.as_raw_mut();
175        let buf_ptr = buf.as_mut_ptr() as *mut libc::c_void;
176        let buf_len = buf.len() as libc::size_t;
177
178        let rc = unsafe {
179            libc::recvfrom(self.0, buf_ptr, buf_len, 0, addr_ptr, &mut addr_len)
180        };
181
182        if rc < 0 {
183            Err(last_os_error())
184        } else {
185            Ok((rc as usize, addr))
186        }
187    }
188
189    /// Blocking send to a socket, given a buffer and address, returning
190    /// the number of bytes sent.
191    ///
192    /// Essentially a wrapper around [libc::sendto].
193    pub fn sendto(&self, buf: &[u8], addr: &MctpSockAddr) -> Result<usize> {
194        let (addr_ptr, addr_len) = addr.as_raw();
195        let buf_ptr = buf.as_ptr() as *const libc::c_void;
196        let buf_len = buf.len() as libc::size_t;
197
198        let rc = unsafe {
199            libc::sendto(self.0, buf_ptr, buf_len, 0, addr_ptr, addr_len)
200        };
201
202        if rc < 0 {
203            Err(last_os_error())
204        } else {
205            Ok(rc as usize)
206        }
207    }
208
209    /// Bind the socket to a local address.
210    pub fn bind(&self, addr: &MctpSockAddr) -> Result<()> {
211        let (addr_ptr, addr_len) = addr.as_raw();
212
213        let rc = unsafe { libc::bind(self.0, addr_ptr, addr_len) };
214
215        if rc < 0 {
216            Err(last_os_error())
217        } else {
218            Ok(())
219        }
220    }
221
222    /// Set the read timeout.
223    ///
224    /// A valid of `None` will have no timeout.
225    pub fn set_read_timeout(&self, dur: Option<Duration>) -> Result<()> {
226        // Avoid warnings about using time_t with musl. See comment in read_timeout().
227        #![allow(deprecated)]
228
229        let dur = dur.unwrap_or(Duration::ZERO);
230        let tv = libc::timeval {
231            tv_sec: dur.as_secs() as libc::time_t,
232            tv_usec: dur.subsec_micros() as libc::suseconds_t,
233        };
234        let rc = unsafe {
235            libc::setsockopt(
236                self.0,
237                libc::SOL_SOCKET,
238                libc::SO_RCVTIMEO,
239                (&tv as *const libc::timeval) as *const libc::c_void,
240                std::mem::size_of::<libc::timeval>() as libc::socklen_t,
241            )
242        };
243
244        if rc < 0 {
245            Err(last_os_error())
246        } else {
247            Ok(())
248        }
249    }
250
251    /// Retrieves the read timeout
252    ///
253    /// A value of `None` indicates no timeout.
254    pub fn read_timeout(&self) -> Result<Option<Duration>> {
255        // Avoid warnings about using time_t with musl. It is safe here since we are
256        // only using it directly with libc, that should be compiled with the
257        // same definitions as libc crate. https://github.com/rust-lang/libc/issues/1848
258        #![allow(deprecated)]
259
260        let mut tv = std::mem::MaybeUninit::<libc::timeval>::uninit();
261        let mut tv_len =
262            std::mem::size_of::<libc::timeval>() as libc::socklen_t;
263        let rc = unsafe {
264            libc::getsockopt(
265                self.0,
266                libc::SOL_SOCKET,
267                libc::SO_RCVTIMEO,
268                tv.as_mut_ptr() as *mut libc::c_void,
269                &mut tv_len as *mut libc::socklen_t,
270            )
271        };
272
273        if rc < 0 {
274            Err(last_os_error())
275        } else {
276            let tv = unsafe { tv.assume_init() };
277            if tv.tv_sec < 0 || tv.tv_usec < 0 {
278                // Negative timeout from socket
279                return Err(mctp::Error::Other);
280            }
281
282            if tv.tv_sec == 0 && tv.tv_usec == 0 {
283                Ok(None)
284            } else {
285                Ok(Some(
286                    Duration::from_secs(tv.tv_sec as u64)
287                        + Duration::from_micros(tv.tv_usec as u64),
288                ))
289            }
290        }
291    }
292}
293
294impl std::os::fd::AsRawFd for MctpSocket {
295    fn as_raw_fd(&self) -> RawFd {
296        self.0
297    }
298}
299
300/// Encapsulation of a remote endpoint: a socket and an Endpoint ID.
301pub struct MctpLinuxReq {
302    eid: Eid,
303    net: u32,
304    sock: MctpSocket,
305    sent: bool,
306}
307
308impl MctpLinuxReq {
309    /// Create a new `MctpLinuxReq` with EID `eid`
310    pub fn new(eid: Eid, net: Option<u32>) -> Result<Self> {
311        let net = net.unwrap_or(MCTP_NET_ANY);
312        Ok(Self {
313            eid,
314            net,
315            sock: MctpSocket::new()?,
316            sent: false,
317        })
318    }
319
320    /// Borrow the internal MCTP socket
321    pub fn as_socket(&mut self) -> &mut MctpSocket {
322        &mut self.sock
323    }
324
325    /// Returns the MCTP Linux network, or None for the default `MCTP_NET_ANY`
326    pub fn net(&self) -> Option<u32> {
327        if self.net == MCTP_NET_ANY {
328            None
329        } else {
330            Some(self.net)
331        }
332    }
333}
334
335impl mctp::ReqChannel for MctpLinuxReq {
336    /// Send a MCTP message
337    ///
338    /// Linux MCTP can also send a preallocated owned tag, but that is not
339    /// yet supported in `MctpLinuxReq`.
340    fn send_vectored(
341        &mut self,
342        typ: MsgType,
343        ic: MsgIC,
344        bufs: &[&[u8]],
345    ) -> Result<()> {
346        let typ_ic = mctp::encode_type_ic(typ, ic);
347        let addr = MctpSockAddr::new(
348            self.eid.0,
349            self.net,
350            typ_ic,
351            mctp::MCTP_TAG_OWNER,
352        );
353        // TODO: implement sendmsg() with iovecs
354        let concat = bufs
355            .iter()
356            .flat_map(|b| b.iter().cloned())
357            .collect::<Vec<u8>>();
358        self.sock.sendto(&concat, &addr)?;
359        self.sent = true;
360        Ok(())
361    }
362
363    fn recv<'f>(
364        &mut self,
365        buf: &'f mut [u8],
366    ) -> Result<(MsgType, MsgIC, &'f mut [u8])> {
367        if !self.sent {
368            return Err(mctp::Error::BadArgument);
369        }
370        let (sz, addr) = self.sock.recvfrom(buf)?;
371        let src = Eid(addr.0.smctp_addr);
372        let (typ, ic) = mctp::decode_type_ic(addr.0.smctp_type);
373        if src != self.eid {
374            // Kernel gave us a message from a different sender?
375            return Err(mctp::Error::Other);
376        }
377        Ok((typ, ic, &mut buf[..sz]))
378    }
379
380    fn remote_eid(&self) -> Eid {
381        self.eid
382    }
383}
384
385/// A Listener for Linux MCTP messages
386pub struct MctpLinuxListener {
387    sock: MctpSocket,
388    net: u32,
389    typ: MsgType,
390}
391
392impl MctpLinuxListener {
393    /// Create a new `MctpLinuxListener`.
394    ///
395    /// This will listen for MCTP message type `typ`, on an optional
396    /// Linux network `net`. `None` network defaults to `MCTP_NET_ANY`.
397    pub fn new(typ: MsgType, net: Option<u32>) -> Result<Self> {
398        let sock = MctpSocket::new()?;
399        // Linux requires MCTP_ADDR_ANY for binds.
400        let net = net.unwrap_or(MCTP_NET_ANY);
401        let addr = MctpSockAddr::new(
402            MCTP_ADDR_ANY.0,
403            net,
404            typ.0,
405            mctp::MCTP_TAG_OWNER,
406        );
407        sock.bind(&addr)?;
408        Ok(Self { sock, net, typ })
409    }
410
411    /// Borrow the internal MCTP socket
412    pub fn as_socket(&mut self) -> &mut MctpSocket {
413        &mut self.sock
414    }
415
416    /// Returns the MCTP Linux network, or None for the default `MCTP_NET_ANY`
417    pub fn net(&self) -> Option<u32> {
418        if self.net == MCTP_NET_ANY {
419            None
420        } else {
421            Some(self.net)
422        }
423    }
424}
425
426impl mctp::Listener for MctpLinuxListener {
427    type RespChannel<'a> = MctpLinuxResp<'a>;
428
429    fn recv<'f>(
430        &mut self,
431        buf: &'f mut [u8],
432    ) -> Result<(MsgType, MsgIC, &'f mut [u8], MctpLinuxResp<'_>)> {
433        let (sz, addr) = self.sock.recvfrom(buf)?;
434        let src = Eid(addr.0.smctp_addr);
435        let (typ, ic) = mctp::decode_type_ic(addr.0.smctp_type);
436        let tag = tag_from_smctp(addr.0.smctp_tag);
437        if let Tag::Unowned(_) = tag {
438            // bind() shouldn't give non-owned packets.
439            return Err(mctp::Error::InternalError);
440        }
441        if typ != self.typ {
442            // bind() should return the requested type
443            return Err(mctp::Error::InternalError);
444        }
445        let ep = MctpLinuxResp {
446            eid: src,
447            tv: tag.tag(),
448            listener: self,
449            typ,
450        };
451        Ok((typ, ic, &mut buf[..sz], ep))
452    }
453}
454
455/// A Linux MCTP Listener response channel
456pub struct MctpLinuxResp<'a> {
457    eid: Eid,
458    // An unowned tag
459    tv: TagValue,
460    listener: &'a MctpLinuxListener,
461    typ: MsgType,
462}
463
464impl mctp::RespChannel for MctpLinuxResp<'_> {
465    type ReqChannel = MctpLinuxReq;
466
467    /// Send a MCTP message
468    ///
469    /// Linux MCTP can also send a preallocated owned tag, but that is not
470    /// yet supported in `MctpLinuxReq`.
471    fn send_vectored(&mut self, ic: MsgIC, bufs: &[&[u8]]) -> Result<()> {
472        let typ_ic = mctp::encode_type_ic(self.typ, ic);
473        let tag = tag_to_smctp(&Tag::Unowned(self.tv));
474        let addr =
475            MctpSockAddr::new(self.eid.0, self.listener.net, typ_ic, tag);
476        // TODO: implement sendmsg() with iovecs
477        let concat = bufs
478            .iter()
479            .flat_map(|b| b.iter().cloned())
480            .collect::<Vec<u8>>();
481        self.listener.sock.sendto(&concat, &addr)?;
482        Ok(())
483    }
484
485    fn remote_eid(&self) -> Eid {
486        self.eid
487    }
488
489    fn req_channel(&self) -> Result<Self::ReqChannel> {
490        MctpLinuxReq::new(self.eid, Some(self.listener.net))
491    }
492}
493
494/// Helper for applications taking an MCTP address as an argument,
495/// configuration, etc.
496///
497/// Address specifications can either be `<eid>`, or `<net>,<eid>`
498///
499/// EID may be either specified in decimal or hex, the latter requiring an '0x'
500/// prefix.
501///
502/// Net must be in decimal.
503///
504/// If no network is specified, the default of MCTP_NET_ANY is used.
505#[derive(Debug)]
506pub struct MctpAddr {
507    eid: Eid,
508    net: Option<u32>,
509}
510
511impl std::str::FromStr for MctpAddr {
512    type Err = String;
513
514    fn from_str(s: &str) -> std::result::Result<MctpAddr, String> {
515        let mut parts = s.split(',');
516
517        let p1 = parts.next();
518        let p2 = parts.next();
519
520        let (net_str, eid_str) = match (p1, p2) {
521            (Some(n), Some(e)) => (Some(n), e),
522            (Some(e), None) => (None, e),
523            _ => return Err("invalid MCTP address format".to_string()),
524        };
525
526        const HEX_PREFIX: &str = "0x";
527        const HEX_PREFIX_LEN: usize = HEX_PREFIX.len();
528
529        let eid = if eid_str.to_ascii_lowercase().starts_with(HEX_PREFIX) {
530            u8::from_str_radix(&eid_str[HEX_PREFIX_LEN..], 16)
531        } else {
532            eid_str.parse()
533        }
534        .map_err(|e| e.to_string())?;
535        let eid = Eid(eid);
536
537        let net: Option<u32> = match net_str {
538            Some(n) => Some(
539                n.parse()
540                    .map_err(|e: std::num::ParseIntError| e.to_string())?,
541            ),
542            None => None,
543        };
544
545        Ok(MctpAddr { net, eid })
546    }
547}
548
549impl MctpAddr {
550    /// Return the MCTP Endpoint ID for this address.
551    pub fn eid(&self) -> Eid {
552        self.eid
553    }
554
555    /// Return the MCTP Network ID for this address, defaulting to MCTP_NET_ANY
556    /// if none was provided originally.
557    pub fn net(&self) -> u32 {
558        self.net.unwrap_or(MCTP_NET_ANY)
559    }
560
561    /// Create an `MctpLinuxReq` using the net & eid values in this address.
562    pub fn create_endpoint(&self) -> Result<MctpLinuxReq> {
563        MctpLinuxReq::new(self.eid, self.net)
564    }
565
566    /// Create an `MCTPListener`.
567    ///
568    /// The net of the listener comes from this address, with the MCTP
569    /// message type as an argument.
570    pub fn create_listener(&self, typ: MsgType) -> Result<MctpLinuxListener> {
571        MctpLinuxListener::new(typ, self.net)
572    }
573}