177 lines
5.3 KiB
Rust
177 lines
5.3 KiB
Rust
use std::io;
|
|
use std::mem;
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
|
|
|
use libc;
|
|
|
|
use super::{ifreq, ifreq_for};
|
|
use crate::wire::ETHERNET_HEADER_LEN;
|
|
|
|
/// set interface
|
|
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
|
|
const BIOCSETIF: libc::c_ulong = 0x8020426c;
|
|
/// get buffer length
|
|
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
|
|
const BIOCGBLEN: libc::c_ulong = 0x40044266;
|
|
/// set immediate/nonblocking read
|
|
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
|
|
const BIOCIMMEDIATE: libc::c_ulong = 0x80044270;
|
|
/// set bpf_hdr struct size
|
|
#[cfg(target_os = "macos")]
|
|
const SIZEOF_BPF_HDR: usize = 18;
|
|
/// set bpf_hdr struct size
|
|
#[cfg(target_os = "openbsd")]
|
|
const SIZEOF_BPF_HDR: usize = 24;
|
|
/// The actual header length may be larger than the bpf_hdr struct due to aligning
|
|
/// see https://github.com/openbsd/src/blob/37ecb4d066e5566411cc16b362d3960c93b1d0be/sys/net/bpf.c#L1649
|
|
/// and https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/bsd/net/bpf.c#L3580
|
|
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
|
|
const BPF_HDRLEN: usize = (((SIZEOF_BPF_HDR + ETHERNET_HEADER_LEN) + mem::align_of::<u32>() - 1)
|
|
& !(mem::align_of::<u32>() - 1))
|
|
- ETHERNET_HEADER_LEN;
|
|
|
|
macro_rules! try_ioctl {
|
|
($fd:expr,$cmd:expr,$req:expr) => {
|
|
unsafe {
|
|
if libc::ioctl($fd, $cmd, $req) == -1 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BpfDevice {
|
|
fd: libc::c_int,
|
|
ifreq: ifreq,
|
|
}
|
|
|
|
impl AsRawFd for BpfDevice {
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.fd
|
|
}
|
|
}
|
|
|
|
fn open_device() -> io::Result<libc::c_int> {
|
|
unsafe {
|
|
for i in 0..256 {
|
|
let dev = format!("/dev/bpf{}\0", i);
|
|
match libc::open(dev.as_ptr() as *const libc::c_char, libc::O_RDWR) {
|
|
-1 => continue,
|
|
fd => return Ok(fd),
|
|
};
|
|
}
|
|
}
|
|
// at this point, all 256 BPF devices were busy and we weren't able to open any
|
|
Err(io::Error::last_os_error())
|
|
}
|
|
|
|
impl BpfDevice {
|
|
pub fn new(name: &str) -> io::Result<BpfDevice> {
|
|
Ok(BpfDevice {
|
|
fd: open_device()?,
|
|
ifreq: ifreq_for(name),
|
|
})
|
|
}
|
|
|
|
pub fn bind_interface(&mut self) -> io::Result<()> {
|
|
try_ioctl!(self.fd, BIOCSETIF, &mut self.ifreq);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// This in fact does not return the interface's mtu,
|
|
/// but it returns the size of the buffer that the app needs to allocate
|
|
/// for the BPF device
|
|
///
|
|
/// The `SIOGIFMTU` cant be called on a BPF descriptor. There is a workaround
|
|
/// to get the actual interface mtu, but this should work better
|
|
///
|
|
/// To get the interface MTU, you would need to create a raw socket first,
|
|
/// and then call `SIOGIFMTU` for the same interface your BPF device is "bound" to.
|
|
/// This MTU that you would get would not include the length of `struct bpf_hdr`
|
|
/// which gets prepended to every packet by BPF,
|
|
/// and your packet will be truncated if it has the length of the MTU.
|
|
///
|
|
/// The buffer size for BPF is usually 4096 bytes, MTU is typically 1500 bytes.
|
|
/// You could do something like `mtu += BPF_HDRLEN`,
|
|
/// but you must change the buffer size the BPF device expects using `BIOCSBLEN` accordingly,
|
|
/// and you must set it before setting the interface with the `BIOCSETIF` ioctl.
|
|
///
|
|
/// The reason I said this should work better is because you might see some unexpected behavior,
|
|
/// truncated/unaligned packets, I/O errors on read()
|
|
/// if you change the buffer size to the actual MTU of the interface.
|
|
pub fn interface_mtu(&mut self) -> io::Result<usize> {
|
|
let mut bufsize: libc::c_int = 1;
|
|
try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int);
|
|
try_ioctl!(self.fd, BIOCGBLEN, &mut bufsize as *mut libc::c_int);
|
|
|
|
Ok(bufsize as usize)
|
|
}
|
|
|
|
pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
|
|
unsafe {
|
|
let len = libc::read(
|
|
self.fd,
|
|
buffer.as_mut_ptr() as *mut libc::c_void,
|
|
buffer.len(),
|
|
);
|
|
|
|
if len == -1 || len < BPF_HDRLEN as isize {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
|
|
let len = len as usize;
|
|
|
|
libc::memmove(
|
|
buffer.as_mut_ptr() as *mut libc::c_void,
|
|
&buffer[BPF_HDRLEN] as *const u8 as *const libc::c_void,
|
|
len - BPF_HDRLEN,
|
|
);
|
|
|
|
Ok(len)
|
|
}
|
|
}
|
|
|
|
pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
|
|
unsafe {
|
|
let len = libc::write(
|
|
self.fd,
|
|
buffer.as_ptr() as *const libc::c_void,
|
|
buffer.len(),
|
|
);
|
|
|
|
if len == -1 {
|
|
Err(io::Error::last_os_error()).unwrap()
|
|
}
|
|
|
|
Ok(len as usize)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for BpfDevice {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
libc::close(self.fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
#[cfg(target_os = "macos")]
|
|
fn test_aligned_bpf_hdr_len() {
|
|
assert_eq!(18, BPF_HDRLEN);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "openbsd")]
|
|
fn test_aligned_bpf_hdr_len() {
|
|
assert_eq!(26, BPF_HDRLEN);
|
|
}
|
|
}
|