bpf.rs 5.4 KB

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