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