1 use std::io;
2 use std::mem;
3 use std::os::unix::io::{AsRawFd, RawFd};
4 
5 use libc;
6 
7 use super::{ifreq, ifreq_for};
8 use crate::phy::Medium;
9 use crate::wire::ETHERNET_HEADER_LEN;
10 
11 /// set interface
12 #[cfg(any(target_os = "macos", target_os = "openbsd"))]
13 const BIOCSETIF: libc::c_ulong = 0x8020426c;
14 /// get buffer length
15 #[cfg(any(target_os = "macos", target_os = "openbsd"))]
16 const BIOCGBLEN: libc::c_ulong = 0x40044266;
17 /// set immediate/nonblocking read
18 #[cfg(any(target_os = "macos", target_os = "openbsd"))]
19 const BIOCIMMEDIATE: libc::c_ulong = 0x80044270;
20 /// set bpf_hdr struct size
21 #[cfg(target_os = "macos")]
22 const SIZEOF_BPF_HDR: usize = 18;
23 /// set bpf_hdr struct size
24 #[cfg(target_os = "openbsd")]
25 const SIZEOF_BPF_HDR: usize = 24;
26 /// The actual header length may be larger than the bpf_hdr struct due to aligning
27 /// see https://github.com/openbsd/src/blob/37ecb4d066e5566411cc16b362d3960c93b1d0be/sys/net/bpf.c#L1649
28 /// and https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/bsd/net/bpf.c#L3580
29 #[cfg(any(target_os = "macos", target_os = "openbsd"))]
30 const BPF_HDRLEN: usize = (((SIZEOF_BPF_HDR + ETHERNET_HEADER_LEN) + mem::align_of::<u32>() - 1)
31     & !(mem::align_of::<u32>() - 1))
32     - ETHERNET_HEADER_LEN;
33 
34 macro_rules! try_ioctl {
35     ($fd:expr,$cmd:expr,$req:expr) => {
36         unsafe {
37             if libc::ioctl($fd, $cmd, $req) == -1 {
38                 return Err(io::Error::last_os_error());
39             }
40         }
41     };
42 }
43 
44 #[derive(Debug)]
45 pub struct BpfDevice {
46     fd: libc::c_int,
47     ifreq: ifreq,
48 }
49 
50 impl AsRawFd for BpfDevice {
as_raw_fd(&self) -> RawFd51     fn as_raw_fd(&self) -> RawFd {
52         self.fd
53     }
54 }
55 
open_device() -> io::Result<libc::c_int>56 fn open_device() -> io::Result<libc::c_int> {
57     unsafe {
58         for i in 0..256 {
59             let dev = format!("/dev/bpf{}\0", i);
60             match libc::open(dev.as_ptr() as *const libc::c_char, libc::O_RDWR) {
61                 -1 => continue,
62                 fd => return Ok(fd),
63             };
64         }
65     }
66     // at this point, all 256 BPF devices were busy and we weren't able to open any
67     Err(io::Error::last_os_error())
68 }
69 
70 impl BpfDevice {
new(name: &str, _medium: Medium) -> io::Result<BpfDevice>71     pub fn new(name: &str, _medium: Medium) -> io::Result<BpfDevice> {
72         Ok(BpfDevice {
73             fd: open_device()?,
74             ifreq: ifreq_for(name),
75         })
76     }
77 
bind_interface(&mut self) -> io::Result<()>78     pub fn bind_interface(&mut self) -> io::Result<()> {
79         try_ioctl!(self.fd, BIOCSETIF, &mut self.ifreq);
80 
81         Ok(())
82     }
83 
84     /// This in fact does not return the interface's mtu,
85     /// but it returns the size of the buffer that the app needs to allocate
86     /// for the BPF device
87     ///
88     /// The `SIOGIFMTU` cant be called on a BPF descriptor. There is a workaround
89     /// to get the actual interface mtu, but this should work better
90     ///
91     /// To get the interface MTU, you would need to create a raw socket first,
92     /// and then call `SIOGIFMTU` for the same interface your BPF device is "bound" to.
93     /// This MTU that you would get would not include the length of `struct bpf_hdr`
94     /// which gets prepended to every packet by BPF,
95     /// and your packet will be truncated if it has the length of the MTU.
96     ///
97     /// The buffer size for BPF is usually 4096 bytes, MTU is typically 1500 bytes.
98     /// You could do something like `mtu += BPF_HDRLEN`,
99     /// but you must change the buffer size the BPF device expects using `BIOCSBLEN` accordingly,
100     /// and you must set it before setting the interface with the `BIOCSETIF` ioctl.
101     ///
102     /// The reason I said this should work better is because you might see some unexpected behavior,
103     /// truncated/unaligned packets, I/O errors on read()
104     /// if you change the buffer size to the actual MTU of the interface.
interface_mtu(&mut self) -> io::Result<usize>105     pub fn interface_mtu(&mut self) -> io::Result<usize> {
106         let mut bufsize: libc::c_int = 1;
107         try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int);
108         try_ioctl!(self.fd, BIOCGBLEN, &mut bufsize as *mut libc::c_int);
109 
110         Ok(bufsize as usize)
111     }
112 
recv(&mut self, buffer: &mut [u8]) -> io::Result<usize>113     pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
114         unsafe {
115             let len = libc::read(
116                 self.fd,
117                 buffer.as_mut_ptr() as *mut libc::c_void,
118                 buffer.len(),
119             );
120 
121             if len == -1 || len < BPF_HDRLEN as isize {
122                 return Err(io::Error::last_os_error());
123             }
124 
125             let len = len as usize;
126 
127             libc::memmove(
128                 buffer.as_mut_ptr() as *mut libc::c_void,
129                 &buffer[BPF_HDRLEN] as *const u8 as *const libc::c_void,
130                 len - BPF_HDRLEN,
131             );
132 
133             Ok(len)
134         }
135     }
136 
send(&mut self, buffer: &[u8]) -> io::Result<usize>137     pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
138         unsafe {
139             let len = libc::write(
140                 self.fd,
141                 buffer.as_ptr() as *const libc::c_void,
142                 buffer.len(),
143             );
144 
145             if len == -1 {
146                 Err(io::Error::last_os_error()).unwrap()
147             }
148 
149             Ok(len as usize)
150         }
151     }
152 }
153 
154 impl Drop for BpfDevice {
drop(&mut self)155     fn drop(&mut self) {
156         unsafe {
157             libc::close(self.fd);
158         }
159     }
160 }
161 
162 #[cfg(test)]
163 mod test {
164     use super::*;
165 
166     #[test]
167     #[cfg(target_os = "macos")]
test_aligned_bpf_hdr_len()168     fn test_aligned_bpf_hdr_len() {
169         assert_eq!(18, BPF_HDRLEN);
170     }
171 
172     #[test]
173     #[cfg(target_os = "openbsd")]
test_aligned_bpf_hdr_len()174     fn test_aligned_bpf_hdr_len() {
175         assert_eq!(26, BPF_HDRLEN);
176     }
177 }
178