bpf.rs 6.0 KB

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