diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/platform/linux.rs | 57 | ||||
| -rw-r--r-- | src/platform/linux/libc_s.rs | 95 | ||||
| -rw-r--r-- | src/platform/linux/rxring.rs | 111 |
3 files changed, 159 insertions, 104 deletions
diff --git a/src/platform/linux.rs b/src/platform/linux.rs index bebc6f4..096451a 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -17,10 +17,10 @@ use crate::opt; mod iptables; mod nftables; mod rxring; +mod libc_s; use iptables::*; use nftables::*; -use libc::sock_filter; use crate::pkt; pub static IS_U32_SUPPORTED: AtomicBool = AtomicBool::new(false); @@ -99,7 +99,7 @@ fn cleanup_rules() -> Result<()> { } fn lock_pid_file() -> Result<()> { - use nix::fcntl::{flock, FlockArg}; + use libc_s::flock; let pid_file = OpenOptions::new() .write(true) @@ -107,7 +107,7 @@ fn lock_pid_file() -> Result<()> { .truncate(false) .open(PID_FILE)?; - if flock(pid_file.as_raw_fd(), FlockArg::LockExclusiveNonblock).is_err() { + if flock(pid_file.as_raw_fd(), libc::LOCK_NB | libc::LOCK_EX).is_err() { let existing_pid = std::fs::read_to_string(PID_FILE)?; anyhow::bail!("Fail to lock {PID_FILE}: {PKG_NAME} already running with PID {}", existing_pid.trim()); } @@ -122,7 +122,7 @@ fn lock_pid_file() -> Result<()> { } fn exit_if_not_root() { - if !nix::unistd::geteuid().is_root() { + if libc_s::geteuid() != 0 { crate::error!("{PKG_NAME} must be run as root. Try sudo."); std::process::exit(3); } @@ -154,7 +154,10 @@ static RAW6: LazyLock<Socket> = LazyLock::new(|| { let sock = Socket::new(Domain::IPV6, Type::RAW, Some(Protocol::TCP)) .expect("create raw6"); - sock.set_header_included_v6(true).expect("IP_HDRINCL"); + if let Err(e) = sock.set_header_included_v6(true) { + crate::warn!("Failed to set IPV6_HDRINCL. Maybe old kernel version? IPv6 header manipulation disabled."); + crate::warn!("Cause: {e}"); + } sock.set_mark(INJECT_MARK).expect("SO_MARK"); sock @@ -181,26 +184,29 @@ pub fn send_to_raw(pkt: &[u8], dst: std::net::IpAddr) -> Result<()> { fn open_nfqueue() -> Result<nfq::Queue> { use std::os::fd::AsRawFd; - use nix::fcntl::{fcntl, FcntlArg, OFlag}; + use libc_s::{fcntl, FcntlArg}; let mut q = nfq::Queue::open()?; - q.bind(crate::opt::queue_num())?; - crate::info!("nfqueue: bound to queue number {}", crate::opt::queue_num()); + q.bind(opt::queue_num())?; + crate::info!("nfqueue: bound to queue number {}", opt::queue_num()); // to check inturrupts - let raw_fd = q.as_raw_fd(); - let flags = fcntl(raw_fd, FcntlArg::F_GETFL)?; - let new_flags = OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK; - fcntl(raw_fd, FcntlArg::F_SETFL(new_flags))?; + let fd = q.as_raw_fd(); + let fl = fcntl(fd, FcntlArg::F_GETFL)?; + fcntl(fd, FcntlArg::F_SETFL(fl | libc::O_NONBLOCK))?; Ok(q) } +/// Open AF_PACKET RX ring for syn/ack packets fn open_rxring() -> Result<rxring::RxRing> { + use libc::sock_filter; + /// cBPF filter for TCP and sport=443 and SYN,ACK packets /// /// Produced by - /// tcpdump -dd '(ip and tcp src port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack)) or (ip6 and tcp src port 443 and ip6[53] & 0x12 == 0x12)' + /// tcpdump -dd '(ip and tcp src port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) + /// == (tcp-syn|tcp-ack)) or (ip6 and tcp src port 443 and ip6[53] & 0x12 == 0x12)' const SYNACK_443_CBPF: &[sock_filter] = &[ sock_filter { code: 0x28, jt: 0, jf: 0, k: 0x0000000c }, sock_filter { code: 0x15, jt: 0, jf: 10, k: 0x00000800 }, @@ -225,12 +231,14 @@ fn open_rxring() -> Result<rxring::RxRing> { sock_filter { code: 0x6, jt: 0, jf: 0, k: 0x00040000 }, sock_filter { code: 0x6, jt: 0, jf: 0, k: 0x00000000 }, ]; + const BLOCK_SIZE: u32 = 4096 * 4; // 16 KB + const BLOCK_NR: u32 = 4; + + /// tpacket_hdr (~66) + eth(14) + ipv6(40) + tcp with options(60) = ~180 + const FRAME_SIZE: u32 = 256; - let rx = rxring::RxRing::new(SYNACK_443_CBPF)?; + let rx = rxring::RxRing::new(SYNACK_443_CBPF, BLOCK_SIZE, BLOCK_NR, FRAME_SIZE)?; crate::info!("rxring: initialized"); - crate::debug!( - "rxring: tcp src port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack)" - ); Ok(rx) } @@ -258,18 +266,6 @@ fn open_signalfd() -> Result<OwnedFd> { } } -// Note: Invalid FDs safely result in POLLNVAL, so this doesn't need to be unsafe -fn poll_s(fds: &mut [libc::pollfd]) -> Result<()> { - use std::io::Error; - - // SAFETY: fds.len() is fds's length - if unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as _, -1) } == -1 { - return Err(Error::last_os_error().into()); - } - - Ok(()) -} - pub fn run() -> Result<()> { use crate::handle_packet; use super::PACKET_SIZE_CAP; @@ -295,7 +291,8 @@ pub fn run() -> Result<()> { crate::splash!("{}", super::MESSAGE_AT_RUN); loop { - poll_s(&mut fds)?; + libc_s::poll(&mut fds, -1)?; + let is_intr: bool = fds[0].revents & libc::POLLIN != 0; let q_ready: bool = fds[1].revents & libc::POLLIN != 0; let rx_ready: bool = fds[2].revents & libc::POLLIN != 0; diff --git a/src/platform/linux/libc_s.rs b/src/platform/linux/libc_s.rs new file mode 100644 index 0000000..f599ed0 --- /dev/null +++ b/src/platform/linux/libc_s.rs @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]> +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::os::fd::{OwnedFd, RawFd, FromRawFd}; +use std::io::Error; + +use std::ffi::{c_int, c_void}; +use std::mem; + +macro_rules! syscall { + ($call:expr) => { + match $call { + -1 => Err(::std::io::Error::last_os_error()), + res => Ok(res), + } + }; +} + +#[allow(non_camel_case_types)] +pub enum FcntlArg { + F_GETFL, + F_SETFL(c_int), +} + +pub fn fcntl(fd: RawFd, op: FcntlArg) -> Result<c_int, Error> { + use libc::fcntl; + + syscall!(match op { + FcntlArg::F_GETFL => unsafe { fcntl(fd, libc::F_GETFL) }, + FcntlArg::F_SETFL(flags) => unsafe { fcntl(fd, libc::F_SETFL, flags) } + }) +} + +pub fn flock(fd: RawFd, op: c_int) -> Result<(), Error> { + syscall!(unsafe { libc::flock(fd, op) }).map(drop) +} + +pub fn geteuid() -> libc::uid_t { + unsafe { libc::geteuid() } +} + +pub fn poll(fds: &mut [libc::pollfd], timeout: c_int) -> Result<(), Error> { + syscall!(unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as _, timeout) }).map(drop) +} + +unsafe fn setsockopt_1<T>(sockfd: RawFd, level: c_int, optname: c_int, optval: &T) -> c_int { + unsafe { + libc::setsockopt(sockfd, level, optname, + (optval as *const T).cast() as *const c_void, + mem::size_of::<T>() as libc::socklen_t) + } +} + +#[allow(non_camel_case_types)] +pub enum SockOpt<'a> { + SO_ATTACH_FILTER(&'a [libc::sock_filter]), + PACKET_RX_RING(&'a libc::tpacket_req), +} + +pub fn setsockopt(sockfd: RawFd, opt: SockOpt) -> Result<(), Error> { + syscall!(match opt { + SockOpt::SO_ATTACH_FILTER(val) => { + let prog = libc::sock_fprog { + len: val.len() as u16, + filter: val.as_ptr() as *mut libc::sock_filter + }; + + unsafe {setsockopt_1(sockfd, libc::SOL_SOCKET, libc::SO_ATTACH_FILTER, &prog)} + }, + SockOpt::PACKET_RX_RING(optval) => unsafe { + setsockopt_1(sockfd, libc::SOL_PACKET, libc::PACKET_RX_RING, optval) + } + }).map(drop) +} + +pub fn socket(domain: c_int, so_type: c_int, protocol: c_int) -> Result<OwnedFd, Error> { + unsafe { + let raw = syscall!(libc::socket(domain, so_type, protocol))?; + Ok(OwnedFd::from_raw_fd(raw)) + } +} + +pub unsafe fn mmap( + addr: *mut c_void, length: usize, prot: c_int, + flags: c_int, fd: RawFd, offset: libc::off_t +) -> Result<*mut c_void, Error> { + match unsafe {libc::mmap(addr, length, prot, flags, fd, offset)} { + libc::MAP_FAILED => Err(Error::last_os_error()), + res => Ok(res), + } +} + +pub unsafe fn munmap(addr: *mut c_void, length: usize) -> Result<(), Error> { + syscall!(unsafe { libc::munmap(addr, length) }).map(drop) +} diff --git a/src/platform/linux/rxring.rs b/src/platform/linux/rxring.rs index e5066dd..0249b9f 100644 --- a/src/platform/linux/rxring.rs +++ b/src/platform/linux/rxring.rs @@ -1,10 +1,14 @@ // SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]> // SPDX-License-Identifier: GPL-3.0-or-later -use std::os::fd::{RawFd, BorrowedFd, AsFd, OwnedFd, FromRawFd, AsRawFd}; +use std::os::fd::{RawFd, BorrowedFd, AsFd, OwnedFd, AsRawFd}; use std::io::Error; use libc::*; +use super::libc_s; + +use libc_s::{setsockopt, SockOpt}; + pub struct RxRing { fd: OwnedFd, ring: *mut u8, @@ -17,85 +21,45 @@ pub struct RxRing { current: usize } -fn attach_filter(sockfd: RawFd, filter: &[sock_filter]) -> Result<(), Error> { - let prog = sock_fprog { - len: filter.len() as u16, - filter: filter.as_ptr() as *mut sock_filter, - }; - - let ret = unsafe { - setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, - &prog as *const _ as *const _, - std::mem::size_of::<sock_fprog>() as socklen_t) - }; - - if ret < 0 { - return Err(Error::last_os_error()); - } - - Ok(()) -} - -/// Make [`sockfd`] as mmapable rxring with size of [`BLOCK_SIZE`] * [`BLOCK_NR`] -/// and single frame [`FRAME_SIZE`] (each packet goes to frame). -/// Since we only need to seek ip header here, 128 bytes are -/// enough. -fn setup_rxring(sockfd: RawFd) -> Result<tpacket_req, Error> { - const BLOCK_SIZE: u32 = 4096 * 4; // 16 KB - const BLOCK_NR: u32 = 4; - - // tpacket_hdr (~66) + eth(14) + ipv6(40) + tcp with options(60) = ~180 - const FRAME_SIZE: u32 = 256; +/// Make [`sockfd`] as mmapable rxring with size of [`tp_block_size`] * [`tp_block_nr`] +/// and single frame [`tp_frame_size`] (each packet goes to frame). +fn setup_rxring(sockfd: RawFd, + tp_block_size: u32, tp_block_nr: u32, tp_frame_size: u32 +) -> Result<tpacket_req, Error> { let req = tpacket_req { - tp_block_size: BLOCK_SIZE, - tp_block_nr: BLOCK_NR, - tp_frame_size: FRAME_SIZE, - tp_frame_nr: BLOCK_SIZE / FRAME_SIZE * BLOCK_NR, + tp_block_size, + tp_block_nr, + tp_frame_size, + tp_frame_nr: tp_block_size / tp_frame_size * tp_block_nr, }; - let ret = unsafe { - setsockopt(sockfd, SOL_PACKET, PACKET_RX_RING, - &req as *const _ as *const _, - std::mem::size_of::<tpacket_req>() as socklen_t) - }; - - if ret < 0 { - return Err(Error::last_os_error()); - } + setsockopt(sockfd, SockOpt::PACKET_RX_RING(&req))?; Ok(req) } impl RxRing { - pub fn new(filter: &[libc::sock_filter]) -> Result<Self, Error> { - let raw = unsafe { - socket( - AF_PACKET, - SOCK_RAW, - (ETH_P_ALL as u16).to_be() as i32 // big-endian - ) - }; - if raw < 0 { return Err(Error::last_os_error()); } - - // SAFETY: we just opened raw. - let fd = unsafe { OwnedFd::from_raw_fd(raw) }; - - attach_filter(fd.as_raw_fd(), filter)?; - let req = setup_rxring(fd.as_raw_fd())?; + pub fn new( + filter: &[libc::sock_filter], + tp_block_size: u32, tp_block_nr: u32, tp_frame_size: u32 + ) -> Result<Self, Error> { + let fd = libc_s::socket(AF_PACKET, SOCK_RAW, (ETH_P_ALL as u16).to_be() as i32)?; + let raw = fd.as_raw_fd(); + + setsockopt(raw, SockOpt::SO_ATTACH_FILTER(&filter))?; + let req = setup_rxring(raw, tp_block_size, tp_block_nr, tp_frame_size)?; let ring_size = (req.tp_block_size * req.tp_block_nr) as usize; - let ring = unsafe { - mmap( - std::ptr::null_mut(), - ring_size, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_LOCKED, - fd.as_raw_fd(), - 0 - ) - }; - if ring == MAP_FAILED { return Err(Error::last_os_error()); } + // SAFETY: we munmap this segment when RxRing is dropped. + let ring = unsafe {libc_s::mmap( + std::ptr::null_mut(), + ring_size, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_LOCKED, + raw, + 0 + )}?; Ok(RxRing { fd, @@ -110,7 +74,6 @@ impl RxRing { let frame_size = self.req.tp_frame_size as usize; // SAFETY: current < frame_nr guaranteed by modular increment on advance. - // ring is valid mmap'd memory from new(), munmapped by Drop. unsafe { self.ring.add(self.current * frame_size) as *mut tpacket_hdr } } @@ -151,12 +114,12 @@ impl AsRawFd for RxRing { } } -// SAFETY: ring was mmap'd with ring_size bytes. -// This guarantees munmap() happens before OwnedFd closes the fd. impl Drop for RxRing { fn drop(&mut self) { - unsafe { - libc::munmap(self.ring as *mut _, self.ring_size); + // SAFETY: ring was mmap'd with ring_size bytes. + match unsafe { libc_s::munmap(self.ring as *mut _, self.ring_size) } { + Err(e) => crate::warn!("rxring: cannot munmap: {}", e.kind()), + Ok(_) => {} } } } |
