dlpi/
lib.rs

1//! This is a crate for interacting with layer-2 network devices through DLPI.
2//! For more details on DLPI see `man dlpi` and `man libdlpi`.
3//!
4//! ```no_run
5//! use std::io::Result;
6//! use std::thread::spawn;
7//!
8//! fn main() -> Result<()> {
9//!     // L2 multicast address to send packets to
10//!     let mc = [0xff, 0xff, 0x00, 0x00, 0x00, 0x47];
11//!
12//!     // Open up an interface called sim0 and attach to the ethertype 0x4000
13//!     let dh_recv = dlpi::open("sim0", 0).expect("open recv");
14//!     dlpi::bind(dh_recv, 0x4000).expect("bind recv");
15//!     dlpi::enable_multicast(dh_recv, &mc).expect("enable multicast");
16//!
17//!     // strt a receiver thread
18//!     let t = spawn(move || {
19//!
20//!         // allocated buffers for a senders L2 address and a message
21//!         let mut src = [0u8; dlpi::sys::DLPI_PHYSADDR_MAX];
22//!         let mut msg = [0; 256];
23//!
24//!         // wait for a message
25//!         let n = match dlpi::recv(dh_recv, &mut src, &mut msg, -1, None) {
26//!             Ok((_, len)) => len,
27//!             Err(e) => panic!("recv: {}", e),
28//!         };
29//!
30//!     });
31//!
32//!     // Open up an interface called sim1 and attach to ethertype 0x4000
33//!     let dh_send = dlpi::open("sim1", 0).expect("send");
34//!     dlpi::bind(dh_send, 0x4000).expect("bind");
35//!
36//!     // Send a message
37//!     let message = b"do you know the muffin man?";
38//!     dlpi::send(dh_send, &mc, &message[..], None).expect("send");
39//!
40//!     // wait on the receiver and then shut down
41//!     t.join().expect("join recv thread");
42//!     dlpi::close(dh_send);
43//!     dlpi::close(dh_recv);
44//!
45//!     Ok(())
46//! }
47//! ```
48
49use num_enum::TryFromPrimitive;
50#[cfg(feature = "async")]
51use std::future::Future;
52use std::io::{Error, ErrorKind, Result};
53use std::os::raw::{c_char, c_void};
54#[cfg(feature = "async")]
55use std::pin::Pin;
56use std::ptr;
57#[cfg(feature = "async")]
58use std::task::{Context, Poll};
59use thiserror::Error;
60
61pub use libdlpi_sys as sys;
62
63/// Result of a DLPI operation.
64#[repr(i32)]
65#[derive(PartialEq, Eq, Error, Debug, Copy, Clone, TryFromPrimitive)]
66pub enum ResultCode {
67    #[error("success")]
68    Success = 10000, /* DLPI operation succeeded */
69    #[error("invalid argument")]
70    EInval, /* invalid argument */
71    #[error("invalid link name")]
72    ELinkNameInval, /* invalid DLPI linkname */
73    #[error("link does not exist")]
74    ENoLink, /* DLPI link does not exist */
75    #[error("bad link")]
76    EBadLink, /* bad DLPI link */
77    #[error("invalid handle")]
78    EInHandle, /* invalid DLPI handle */
79    #[error("operation timed out")]
80    ETimedout, /* DLPI operation timed out */
81    #[error("unsupported version")]
82    EVerNotSup, /* unsupported DLPI Version */
83    #[error("unsupported connection mode")]
84    EModeNotSup, /* unsupported DLPI connection mode */
85    #[error("unavailable service access point")]
86    EUnavailSAP, /* unavailable DLPI SAP */
87    #[error("failure")]
88    Failure, /* DLPI operation failed */
89    #[error("style-2 node reports style-1")]
90    ENotStyle2, /* DLPI style-2 node reports style-1 */
91    #[error("bad message")]
92    EBadMsg, /* bad DLPI message */
93    #[error("raw mode not supported")]
94    ERawNotSup, /* DLPI raw mode not supported */
95    #[error("invalid notification type")]
96    ENoteInval, /* invalid DLPI notification type */
97    #[error("notification not supported by link")]
98    ENoteNotSup, /* DLPI notification not supported by link */
99    #[error("invalid notification id")]
100    ENoteIdInval, /* invalid DLPI notification id */
101    #[error("ipnetinfo not supported")]
102    EIpNetInfoNotSup, /* DLPI_IPNETINFO not supported */
103    #[error("error max")]
104    ErrMax, /* Highest + 1 libdlpi error code */
105}
106
107/// A DLPI handle wrapper that implements `Send` and `Sync`.
108#[derive(Clone, Copy)]
109pub struct DlpiHandle(pub *mut sys::dlpi_handle_t);
110unsafe impl Send for DlpiHandle {}
111unsafe impl Sync for DlpiHandle {}
112
113/// A wrapper for DlpiHandle that closes the DLPI instance when dropped.
114pub struct DropHandle(pub DlpiHandle);
115impl Drop for DropHandle {
116    /// Closes underlying DLPI instance.
117    fn drop(&mut self) {
118        close(self.0);
119    }
120}
121
122impl DropHandle {
123    /// Get the filesystem descriptor associated with this drop handle.
124    pub fn fd(&self) -> Result<i32> {
125        fd(self.0)
126    }
127}
128
129/// Creates a DLPI link instance.
130pub fn open(linkname: impl AsRef<str>, flags: u32) -> Result<DlpiHandle> {
131    let linkname = format!("{}\0", linkname.as_ref());
132
133    let mut dhp = sys::null_dlpi_handle();
134    let ret = unsafe {
135        sys::dlpi_open(
136            linkname.as_str().as_ptr() as *const c_char,
137            &mut dhp,
138            flags,
139        )
140    };
141    check_return(ret)?;
142    Ok(DlpiHandle(dhp))
143}
144
145/// Send a message over a DLPI link.
146pub fn send(
147    h: DlpiHandle,
148    dst: &[u8],
149    msg: &[u8],
150    info: Option<&sys::dlpi_sendinfo_t>,
151) -> Result<()> {
152    let ret = unsafe {
153        sys::dlpi_send(
154            h.0,
155            dst.as_ptr() as *const c_void,
156            dst.len(),
157            msg.as_ptr() as *const c_void,
158            msg.len(),
159            match info {
160                Some(info) => info as *const sys::dlpi_sendinfo_t,
161                None => ptr::null(),
162            },
163        )
164    };
165
166    check_return(ret)?;
167    Ok(())
168}
169
170/// Receive a message from a DLPI link.
171///
172/// Data is placed into provided buffer.  Return values is (address bytes read,
173/// message bytes read).
174///
175/// If no message is received within `msec` milliseconds, returns
176/// [`ResultCode::ETimedout`].
177///
178/// **`src` must be at least [`sys::DLPI_PHYSADDR_MAX`] in length**.
179pub fn recv(
180    h: DlpiHandle,
181    src: &mut [u8],
182    msg: &mut [u8],
183    msec: i32,
184    info: Option<&mut sys::dlpi_recvinfo_t>,
185) -> Result<(usize, usize)> {
186    let mut src_read = src.len();
187    let mut msg_read = msg.len();
188    let ret = unsafe {
189        sys::dlpi_recv(
190            h.0,
191            src.as_mut_ptr() as *mut c_void,
192            &mut src_read,
193            msg.as_mut_ptr() as *mut c_void,
194            &mut msg_read,
195            msec,
196            match info {
197                Some(info) => info as *mut sys::dlpi_recvinfo_t,
198                None => ptr::null_mut(),
199            },
200        )
201    };
202
203    check_return(ret)?;
204    Ok((src_read, msg_read))
205}
206
207#[cfg(feature = "async")]
208/// A receiver object returned from [`recv_async`] wrapped in a future. Calling
209/// `await` on this object yields the same result as [`recv`].
210pub struct DlpiRecv<'a> {
211    h: DlpiHandle,
212    src: &'a mut [u8],
213    msg: &'a mut [u8],
214    info: Option<&'a mut sys::dlpi_recvinfo_t>,
215}
216
217/// An `async` version of [`recv`]. Calling `await` on result yields same
218/// result as [`recv`].
219///
220/// **`src` must be at least [`sys::DLPI_PHYSADDR_MAX`] in length**.
221/*pub fn recv_async<'a>(
222    h: DlpiHandle,
223    src: &'a mut [u8],
224    msg: &'a mut [u8],
225    info: Option<&'a mut sys::dlpi_recvinfo_t>,
226) -> DlpiRecv<'a> {
227    DlpiRecv::<'a> { h, src, msg, info }
228}
229*/
230
231#[cfg(feature = "async")]
232pub async fn recv_async<'a>(
233    h: DlpiHandle,
234    src: &'a mut [u8],
235    msg: &'a mut [u8],
236    info: Option<&'a mut sys::dlpi_recvinfo_t>,
237) -> Result<(usize, usize)> {
238    let afd = tokio::io::unix::AsyncFd::new(fd(h)?)?;
239    let mut _guard = afd.readable().await?;
240    recv(
241        h, src, msg, 0, // non blocking
242        info,
243    )
244}
245
246#[cfg(feature = "async")]
247impl<'a> Future for DlpiRecv<'a> {
248    type Output = Result<(usize, usize)>;
249
250    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
251        let mut src_read = self.src.len();
252        let mut msg_read = self.msg.len();
253        let s = self.get_mut();
254
255        let ret = unsafe {
256            sys::dlpi_recv(
257                s.h.0,
258                s.src.as_mut_ptr() as *mut c_void,
259                &mut src_read,
260                s.msg.as_mut_ptr() as *mut c_void,
261                &mut msg_read,
262                0, // non blocking
263                match s.info {
264                    Some(ref mut info) => *info as *mut sys::dlpi_recvinfo_t,
265                    None => ptr::null_mut(),
266                },
267            )
268        };
269
270        if ret == ResultCode::Success as i32 {
271            Poll::Ready(Ok((src_read, msg_read)))
272        } else if ret == ResultCode::ETimedout as i32 {
273            cx.waker().wake_by_ref();
274            Poll::Pending
275        } else {
276            Poll::Ready(Err(to_io_error(ret)))
277        }
278    }
279}
280
281/// Bind a DLPI link to a service access point type.
282///
283/// This will restrict the DLPI link to only operate on the provided service
284/// access point. For ethernet the service access point is the ethertype.
285pub fn bind(h: DlpiHandle, sap: u32) -> Result<u32> {
286    let mut bound_sap = 0;
287    let ret = unsafe { sys::dlpi_bind(h.0, sap, &mut bound_sap) };
288
289    check_return(ret)?;
290    Ok(bound_sap)
291}
292
293/// Enable reception of messages destined to the provided layer-2 address.
294///
295/// In most cases the layer 2 address will be a mac address. For example,
296/// something in the range `33:33:00:00:00:00`-`33:33:FF:FF:FF:FF` for IPv6
297/// multicast.
298pub fn enable_multicast(h: DlpiHandle, addr: &[u8]) -> Result<()> {
299    let ret = unsafe {
300        sys::dlpi_enabmulti(h.0, addr.as_ptr() as *const c_void, addr.len())
301    };
302
303    check_return(ret)?;
304    Ok(())
305}
306
307/// Disable reception of messages destined to the provided layer-2 address.
308///
309/// In most cases the layer 2 address will be a mac address. For example,
310/// something in the range `33:33:00:00:00:00`-`33:33:FF:FF:FF:FF` for IPv6
311/// multicast.
312pub fn disable_multicast(h: DlpiHandle, addr: &[u8]) -> Result<()> {
313    let ret = unsafe {
314        sys::dlpi_disabmulti(h.0, addr.as_ptr() as *const c_void, addr.len())
315    };
316
317    check_return(ret)?;
318    Ok(())
319}
320
321/// Enable promiscuous mode for the specified handle. See DL_PROMISC_* for
322/// levels.
323pub fn promisc_on(h: DlpiHandle, level: u32) -> Result<()> {
324    let ret = unsafe { sys::dlpi_promiscon(h.0, level) };
325    match ret {
326        -1 => Err(Error::from_raw_os_error(libc::EINVAL)),
327        _ => Ok(()),
328    }
329}
330
331/// Disable promiscuous mode for the specified handle. See DL_PROMISC_* for
332/// levels.
333pub fn promisc_off(h: DlpiHandle, level: u32) -> Result<()> {
334    let ret = unsafe { sys::dlpi_promiscoff(h.0, level) };
335    match ret {
336        -1 => Err(Error::from_raw_os_error(libc::EINVAL)),
337        _ => Ok(()),
338    }
339}
340
341/// Get a file descriptor associated with the provided handle.
342pub fn fd(h: DlpiHandle) -> Result<i32> {
343    let ret = unsafe { sys::dlpi_fd(h.0) };
344    match ret {
345        -1 => Err(Error::from_raw_os_error(libc::EINVAL)),
346        _ => Ok(ret),
347    }
348}
349
350/// Close the provided handle.
351pub fn close(h: DlpiHandle) {
352    unsafe { sys::dlpi_close(h.0) };
353}
354
355fn check_return(ret: i32) -> Result<()> {
356    if ret == ResultCode::Success as i32 {
357        return Ok(());
358    }
359
360    Err(to_io_error(ret))
361}
362
363fn to_io_error(ret: i32) -> Error {
364    if ret == sys::DL_SYSERR {
365        return Error::last_os_error();
366    }
367
368    match ResultCode::try_from(ret) {
369        Ok(rc) => Error::new(ErrorKind::Other, rc),
370        Err(_) => Error::from_raw_os_error(ret),
371    }
372}
373
374#[cfg(test)]
375mod test;