1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
use std::os::fd::{RawFd, BorrowedFd, AsFd, OwnedFd, FromRawFd, AsRawFd};
use std::io::Error;
use libc::*;
pub struct RxRing {
fd: OwnedFd,
ring: *mut u8,
/// Bytes of mmap'd [`ring`]
ring_size: usize,
req: tpacket_req,
/// Current frame index in the ring buffer (0..req.tp_frame_nr)
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;
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,
};
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());
}
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())?;
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()); }
Ok(RxRing {
fd,
ring: ring as *mut u8,
ring_size,
req,
current: 0,
})
}
fn current_frame(&self) -> *mut tpacket_hdr {
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 }
}
pub fn current_packet(&self) -> Option<&[u8]> {
let hdr = unsafe { &*(self.current_frame()) };
// Check if we have permission from kernel to use current frame.
if hdr.tp_status & TP_STATUS_USER as u64 == 0 {
return None;
}
// SAFETY: tp_net and tp_snaplen are valid when tp_status == TP_STATUS_USER.
let data = unsafe {
let ptr = (hdr as *const tpacket_hdr as *const u8).add(hdr.tp_net as usize);
std::slice::from_raw_parts(ptr, (*hdr).tp_snaplen as usize)
};
Some(data)
}
pub fn advance(&mut self) {
// SAFETY: see current_frame
unsafe { (*self.current_frame()).tp_status = TP_STATUS_KERNEL as u64; }
self.current = (self.current + 1) % self.req.tp_frame_nr as usize;
}
}
impl AsFd for RxRing {
fn as_fd(&self) -> BorrowedFd<'_> {
self.fd.as_fd()
}
}
impl AsRawFd for RxRing {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
// 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);
}
}
}
|