1use std::ffi::CString;
11use std::io;
12use std::os::fd::RawFd;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum BpfFilterStatus {
17 Attached(RawFd),
19 FallbackUnprivileged,
21 Unsupported,
23}
24
25pub const BPF_MAP_CREATE: libc::c_int = 0;
27pub const BPF_MAP_UPDATE_ELEM: libc::c_int = 2;
28pub const BPF_PROG_LOAD: libc::c_int = 5;
29pub const BPF_LINK_CREATE: libc::c_int = 28;
30
31pub const BPF_MAP_TYPE_XSKMAP: u32 = 17;
33pub const BPF_PROG_TYPE_XDP: u32 = 6;
34pub const BPF_XDP: u32 = 37;
35
36pub const BPF_FUNC_REDIRECT_MAP: i32 = 51;
38
39pub const XDP_PASS: i32 = 2;
41
42pub const BPF_PSEUDO_MAP_FD: u8 = 1;
44
45pub const BPF_LD: u8 = 0x00;
47pub const BPF_LDX: u8 = 0x01;
48pub const BPF_ALU64: u8 = 0x07;
49pub const BPF_JMP: u8 = 0x05;
50
51pub const BPF_W: u8 = 0x00;
52pub const BPF_H: u8 = 0x08;
53pub const BPF_B: u8 = 0x10;
54pub const BPF_DW: u8 = 0x18;
55
56pub const BPF_IMM: u8 = 0x00;
57pub const BPF_MEM: u8 = 0x60;
58
59pub const BPF_K: u8 = 0x00;
60pub const BPF_X: u8 = 0x08;
61
62pub const BPF_ADD: u8 = 0x00;
63pub const BPF_AND: u8 = 0x50;
64pub const BPF_LSH: u8 = 0x60;
65pub const BPF_MOV: u8 = 0xb0;
66
67pub const BPF_JA: u8 = 0x00;
68pub const BPF_JEQ: u8 = 0x10;
69pub const BPF_JGT: u8 = 0x20;
70pub const BPF_JNE: u8 = 0x50;
71pub const BPF_CALL: u8 = 0x80;
72pub const BPF_EXIT: u8 = 0x90;
73
74pub const R0: u8 = 0;
76pub const R1: u8 = 1;
77pub const R2: u8 = 2;
78pub const R3: u8 = 3;
79pub const R4: u8 = 4;
80pub const R5: u8 = 5;
81pub const R6: u8 = 6;
82
83#[repr(C)]
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub struct BpfInsn {
87 pub code: u8,
88 pub regs: u8, pub off: i16,
90 pub imm: i32,
91}
92
93impl BpfInsn {
94 pub const fn new(code: u8, dst: u8, src: u8, off: i16, imm: i32) -> Self {
95 Self {
96 code,
97 regs: ((src & 0x0f) << 4) | (dst & 0x0f),
98 off,
99 imm,
100 }
101 }
102
103 pub const fn mov64_reg(dst: u8, src: u8) -> Self {
104 Self::new(BPF_ALU64 | BPF_MOV | BPF_X, dst, src, 0, 0)
105 }
106
107 pub const fn mov64_imm(dst: u8, imm: i32) -> Self {
108 Self::new(BPF_ALU64 | BPF_MOV | BPF_K, dst, 0, 0, imm)
109 }
110
111 pub const fn add64_imm(dst: u8, imm: i32) -> Self {
112 Self::new(BPF_ALU64 | BPF_ADD | BPF_K, dst, 0, 0, imm)
113 }
114
115 pub const fn add64_reg(dst: u8, src: u8) -> Self {
116 Self::new(BPF_ALU64 | BPF_ADD | BPF_X, dst, src, 0, 0)
117 }
118
119 pub const fn and64_imm(dst: u8, imm: i32) -> Self {
120 Self::new(BPF_ALU64 | BPF_AND | BPF_K, dst, 0, 0, imm)
121 }
122
123 pub const fn lsh64_imm(dst: u8, imm: i32) -> Self {
124 Self::new(BPF_ALU64 | BPF_LSH | BPF_K, dst, 0, 0, imm)
125 }
126
127 pub const fn ja(off: i16) -> Self {
128 Self::new(BPF_JMP | BPF_JA, 0, 0, off, 0)
129 }
130
131 pub const fn jeq_imm(dst: u8, imm: i32, off: i16) -> Self {
132 Self::new(BPF_JMP | BPF_JEQ | BPF_K, dst, 0, off, imm)
133 }
134
135 pub const fn ldx_mem_w(dst: u8, src: u8, off: i16) -> Self {
136 Self::new(BPF_LDX | BPF_MEM | BPF_W, dst, src, off, 0)
137 }
138
139 pub const fn ldx_mem_h(dst: u8, src: u8, off: i16) -> Self {
140 Self::new(BPF_LDX | BPF_MEM | BPF_H, dst, src, off, 0)
141 }
142
143 pub const fn ldx_mem_b(dst: u8, src: u8, off: i16) -> Self {
144 Self::new(BPF_LDX | BPF_MEM | BPF_B, dst, src, off, 0)
145 }
146
147 pub const fn jgt_reg(dst: u8, src: u8, off: i16) -> Self {
148 Self::new(BPF_JMP | BPF_JGT | BPF_X, dst, src, off, 0)
149 }
150
151 pub const fn jne_imm(dst: u8, imm: i32, off: i16) -> Self {
152 Self::new(BPF_JMP | BPF_JNE | BPF_K, dst, 0, off, imm)
153 }
154
155 pub const fn call_helper(func: i32) -> Self {
156 Self::new(BPF_JMP | BPF_CALL, 0, 0, 0, func)
157 }
158
159 pub const fn exit() -> Self {
160 Self::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0)
161 }
162}
163
164#[repr(C)]
165#[derive(Clone, Copy)]
166pub struct BpfMapCreateAttr {
167 pub map_type: u32,
168 pub key_size: u32,
169 pub value_size: u32,
170 pub max_entries: u32,
171 pub map_flags: u32,
172}
173
174#[repr(C)]
175#[derive(Clone, Copy)]
176pub struct BpfMapElemAttr {
177 pub map_fd: u32,
178 pub _pad: u32,
179 pub key: u64,
180 pub value: u64,
181 pub flags: u64,
182}
183
184#[repr(C)]
185#[derive(Clone, Copy)]
186pub struct BpfProgLoadAttr {
187 pub prog_type: u32,
188 pub insn_cnt: u32,
189 pub insns: u64,
190 pub license: u64,
191 pub log_level: u32,
192 pub log_size: u32,
193 pub log_buf: u64,
194 pub kern_version: u32,
195 pub prog_flags: u32,
196}
197
198#[repr(C)]
199#[derive(Clone, Copy)]
200pub struct BpfLinkCreateAttr {
201 pub prog_fd: u32,
202 pub target_ifindex: u32,
203 pub attach_type: u32,
204 pub flags: u32,
205}
206
207#[repr(C)]
209pub union BpfAttr {
210 pub map_create: BpfMapCreateAttr,
211 pub map_elem: BpfMapElemAttr,
212 pub prog_load: BpfProgLoadAttr,
213 pub link_create: BpfLinkCreateAttr,
214 pub pad: [u8; 152],
215}
216
217pub unsafe fn sys_bpf(cmd: libc::c_int, attr: *const libc::c_void, size: usize) -> libc::c_long {
224 libc::syscall(libc::SYS_bpf, cmd, attr, size)
226}
227
228pub fn bpf_map_create_xsk(max_entries: u32) -> io::Result<RawFd> {
230 let mut attr = BpfAttr { pad: [0u8; 152] };
231 attr.map_create = BpfMapCreateAttr {
232 map_type: BPF_MAP_TYPE_XSKMAP,
233 key_size: 4,
234 value_size: 4,
235 max_entries,
236 map_flags: 0,
237 };
238
239 let res = unsafe {
242 sys_bpf(
243 BPF_MAP_CREATE,
244 std::ptr::addr_of!(attr).cast::<libc::c_void>(),
245 std::mem::size_of::<BpfAttr>(),
246 )
247 };
248
249 if res < 0 {
250 return Err(io::Error::last_os_error());
251 }
252 Ok(res as RawFd)
253}
254
255pub fn bpf_map_update_xsk(map_fd: RawFd, queue_id: u32, xsk_fd: RawFd) -> io::Result<()> {
257 let mut attr = BpfAttr { pad: [0u8; 152] };
258 let key = queue_id;
259 let value = xsk_fd as u32;
260
261 attr.map_elem = BpfMapElemAttr {
262 map_fd: map_fd as u32,
263 _pad: 0,
264 key: std::ptr::addr_of!(key) as u64,
265 value: std::ptr::addr_of!(value) as u64,
266 flags: 0,
267 };
268
269 let res = unsafe {
273 sys_bpf(
274 BPF_MAP_UPDATE_ELEM,
275 std::ptr::addr_of!(attr).cast::<libc::c_void>(),
276 std::mem::size_of::<BpfAttr>(),
277 )
278 };
279
280 if res < 0 {
281 return Err(io::Error::last_os_error());
282 }
283 Ok(())
284}
285
286pub fn bpf_prog_assemble_xdp(listen_port: u16, xsk_map_fd: RawFd) -> Vec<BpfInsn> {
291 let eth_p_ip = (0x0800u16).to_be() as i32;
292 let eth_p_ipv6 = (0x86ddu16).to_be() as i32;
293 let udp_port = listen_port.to_be() as i32;
294
295 vec![
296 BpfInsn::mov64_reg(R6, R1),
298 BpfInsn::ldx_mem_w(R2, R6, 0),
300 BpfInsn::ldx_mem_w(R3, R6, 4),
302 BpfInsn::mov64_reg(R4, R2),
304 BpfInsn::add64_imm(R4, 14),
306 BpfInsn::jgt_reg(R4, R3, 33),
308 BpfInsn::ldx_mem_h(R4, R2, 12),
310 BpfInsn::jeq_imm(R4, eth_p_ip, 9),
312 BpfInsn::jne_imm(R4, eth_p_ipv6, 30),
314 BpfInsn::mov64_reg(R4, R2),
317 BpfInsn::add64_imm(R4, 62),
319 BpfInsn::jgt_reg(R4, R3, 27),
321 BpfInsn::ldx_mem_b(R4, R2, 20),
323 BpfInsn::jne_imm(R4, 17, 25),
325 BpfInsn::ldx_mem_h(R4, R2, 56),
327 BpfInsn::jne_imm(R4, udp_port, 23),
329 BpfInsn::ja(16),
331 BpfInsn::mov64_reg(R4, R2),
334 BpfInsn::add64_imm(R4, 42),
336 BpfInsn::jgt_reg(R4, R3, 19),
338 BpfInsn::ldx_mem_b(R4, R2, 23),
340 BpfInsn::jne_imm(R4, 17, 17),
342 BpfInsn::ldx_mem_b(R5, R2, 14),
344 BpfInsn::and64_imm(R5, 0x0f),
346 BpfInsn::lsh64_imm(R5, 2),
348 BpfInsn::mov64_reg(R4, R2),
350 BpfInsn::add64_imm(R4, 14),
352 BpfInsn::add64_reg(R4, R5),
354 BpfInsn::mov64_reg(R5, R4),
356 BpfInsn::add64_imm(R5, 8),
358 BpfInsn::jgt_reg(R5, R3, 8),
360 BpfInsn::ldx_mem_h(R4, R4, 2),
362 BpfInsn::jne_imm(R4, udp_port, 6),
364 BpfInsn::new(
367 BPF_LD | BPF_DW | BPF_IMM,
368 R1,
369 BPF_PSEUDO_MAP_FD,
370 0,
371 xsk_map_fd,
372 ),
373 BpfInsn::new(0, 0, 0, 0, 0),
374 BpfInsn::ldx_mem_w(R2, R6, 16),
376 BpfInsn::mov64_imm(R3, 0),
378 BpfInsn::call_helper(BPF_FUNC_REDIRECT_MAP),
380 BpfInsn::exit(),
382 BpfInsn::mov64_imm(R0, XDP_PASS),
385 BpfInsn::exit(),
387 ]
388}
389
390pub fn bpf_prog_load_xdp(listen_port: u16, xsk_map_fd: RawFd) -> io::Result<RawFd> {
392 let insns = bpf_prog_assemble_xdp(listen_port, xsk_map_fd);
393 let license = b"GPL\0";
394
395 let mut attr = BpfAttr { pad: [0u8; 152] };
396 attr.prog_load = BpfProgLoadAttr {
397 prog_type: BPF_PROG_TYPE_XDP,
398 insn_cnt: insns.len() as u32,
399 insns: insns.as_ptr() as u64,
400 license: license.as_ptr() as u64,
401 log_level: 0,
402 log_size: 0,
403 log_buf: 0,
404 kern_version: 0,
405 prog_flags: 0,
406 };
407
408 let res = unsafe {
411 sys_bpf(
412 BPF_PROG_LOAD,
413 std::ptr::addr_of!(attr).cast::<libc::c_void>(),
414 std::mem::size_of::<BpfAttr>(),
415 )
416 };
417
418 if res < 0 {
419 return Err(io::Error::last_os_error());
420 }
421 Ok(res as RawFd)
422}
423
424fn netlink_attach_xdp(ifindex: u32, prog_fd: RawFd) -> io::Result<RawFd> {
426 let nl_sock = unsafe {
428 libc::socket(
429 libc::AF_NETLINK,
430 libc::SOCK_RAW | libc::SOCK_CLOEXEC,
431 libc::NETLINK_ROUTE,
432 )
433 };
434 if nl_sock < 0 {
435 return Err(io::Error::last_os_error());
436 }
437 let _sock_guard = AutoCloseFd(nl_sock);
438
439 #[repr(C)]
440 struct NlReq {
441 nlh: libc::nlmsghdr,
442 ifi: libc::ifinfomsg,
443 xdp_hdr: libc::nlattr,
444 fd_hdr: libc::nlattr,
445 fd_val: i32,
446 }
447
448 let fd_attr_len = std::mem::size_of::<libc::nlattr>() + std::mem::size_of::<i32>();
449 let xdp_attr_len = std::mem::size_of::<libc::nlattr>() + fd_attr_len;
450 let total_len = std::mem::size_of::<libc::nlmsghdr>()
451 + std::mem::size_of::<libc::ifinfomsg>()
452 + xdp_attr_len;
453
454 let mut req: NlReq = unsafe { std::mem::zeroed() };
456 req.nlh.nlmsg_len = total_len as u32;
457 req.nlh.nlmsg_type = libc::RTM_SETLINK;
458 req.nlh.nlmsg_flags = (libc::NLM_F_REQUEST | libc::NLM_F_ACK) as u16;
459 req.nlh.nlmsg_seq = 1;
460 req.ifi.ifi_family = libc::AF_UNSPEC as u8;
461 req.ifi.ifi_index = ifindex as i32;
462
463 req.xdp_hdr.nla_len = xdp_attr_len as u16;
464 req.xdp_hdr.nla_type = libc::NLA_F_NESTED as u16 | 43; req.fd_hdr.nla_len = fd_attr_len as u16;
467 req.fd_hdr.nla_type = 1; req.fd_val = prog_fd;
469
470 let send_ret = unsafe {
472 libc::send(
473 nl_sock,
474 std::ptr::addr_of!(req).cast::<libc::c_void>(),
475 total_len,
476 0,
477 )
478 };
479 if send_ret < 0 {
480 return Err(io::Error::last_os_error());
481 }
482
483 let mut buf = [0u8; 256];
484 let recv_ret = unsafe {
486 libc::recv(
487 nl_sock,
488 buf.as_mut_ptr().cast::<libc::c_void>(),
489 buf.len(),
490 0,
491 )
492 };
493 if recv_ret < 0 {
494 return Err(io::Error::last_os_error());
495 }
496
497 if recv_ret as usize
498 >= std::mem::size_of::<libc::nlmsghdr>() + std::mem::size_of::<libc::nlmsgerr>()
499 {
500 let nlh = unsafe { std::ptr::read_unaligned(buf.as_ptr().cast::<libc::nlmsghdr>()) };
502 if nlh.nlmsg_type == libc::NLMSG_ERROR as u16 {
503 let err = unsafe {
505 std::ptr::read_unaligned(
506 buf.as_ptr()
507 .add(std::mem::size_of::<libc::nlmsghdr>())
508 .cast::<libc::nlmsgerr>(),
509 )
510 };
511 if err.error != 0 {
512 return Err(io::Error::from_raw_os_error(-err.error));
513 }
514 }
515 }
516
517 Ok(prog_fd)
518}
519
520pub fn bpf_attach_xdp(ifindex: u32, prog_fd: RawFd) -> io::Result<RawFd> {
524 let mut attr = BpfAttr { pad: [0u8; 152] };
526 attr.link_create = BpfLinkCreateAttr {
527 prog_fd: prog_fd as u32,
528 target_ifindex: ifindex,
529 attach_type: BPF_XDP,
530 flags: 0,
531 };
532
533 let res = unsafe {
535 sys_bpf(
536 BPF_LINK_CREATE,
537 std::ptr::addr_of!(attr).cast::<libc::c_void>(),
538 std::mem::size_of::<BpfAttr>(),
539 )
540 };
541
542 if res >= 0 {
543 return Ok(res as RawFd);
544 }
545
546 let link_err = io::Error::last_os_error();
547 match link_err.raw_os_error() {
548 Some(libc::EINVAL | libc::ENOSYS | libc::EOPNOTSUPP) => {
549 netlink_attach_xdp(ifindex, prog_fd)
551 }
552 _ => Err(link_err),
553 }
554}
555
556pub(crate) struct AutoCloseFd(pub RawFd);
558
559impl AutoCloseFd {
560 pub fn into_raw(mut self) -> RawFd {
561 let fd = self.0;
562 self.0 = -1;
563 fd
564 }
565}
566
567impl Drop for AutoCloseFd {
568 fn drop(&mut self) {
569 if self.0 >= 0 {
570 unsafe {
572 libc::close(self.0);
573 }
574 }
575 }
576}
577
578pub struct XdpRedirectFilter {
580 map_fd: RawFd,
581 prog_fd: RawFd,
582}
583
584impl XdpRedirectFilter {
585 pub fn new(map_fd: RawFd, prog_fd: RawFd) -> Self {
587 Self { map_fd, prog_fd }
588 }
589
590 pub fn map_fd(&self) -> RawFd {
592 self.map_fd
593 }
594
595 pub fn prog_fd(&self) -> RawFd {
597 self.prog_fd
598 }
599
600 pub fn into_raw(mut self) -> (RawFd, RawFd) {
603 let map_fd = self.map_fd;
604 let prog_fd = self.prog_fd;
605 self.map_fd = -1;
606 self.prog_fd = -1;
607 (map_fd, prog_fd)
608 }
609
610 pub fn set_socket_for_queue(&mut self, queue_id: u32, xsk_fd: RawFd) -> io::Result<()> {
612 if self.map_fd < 0 {
613 return Err(io::Error::from_raw_os_error(libc::EBADF));
614 }
615 if xsk_fd < 0 {
616 return Err(io::Error::from_raw_os_error(libc::EBADF));
617 }
618 bpf_map_update_xsk(self.map_fd, queue_id, xsk_fd)
619 }
620
621 pub fn load_multi_queue(
624 ifname: &str,
625 listen_port: u16,
626 queue_fds: &[(u32, RawFd)],
627 ) -> Result<Self, BpfFilterStatus> {
628 let c_ifname = match CString::new(ifname) {
629 Ok(c) => c,
630 Err(_) => return Err(BpfFilterStatus::Unsupported),
631 };
632
633 let ifindex = unsafe { libc::if_nametoindex(c_ifname.as_ptr()) };
635 if ifindex == 0 {
636 return Err(BpfFilterStatus::Unsupported);
637 }
638
639 let max_queue_id = queue_fds.iter().map(|(q, _)| *q).max().unwrap_or(0);
641 let max_entries = max_queue_id
642 .checked_add(1)
643 .unwrap_or(64)
644 .max(queue_fds.len() as u32)
645 .max(64);
646
647 let map_fd = match bpf_map_create_xsk(max_entries) {
648 Ok(fd) => fd,
649 Err(e) => {
650 return match e.raw_os_error() {
651 Some(libc::EPERM | libc::EACCES) => Err(BpfFilterStatus::FallbackUnprivileged),
652 _ => Err(BpfFilterStatus::Unsupported),
653 };
654 }
655 };
656 let map_guard = AutoCloseFd(map_fd);
657
658 for &(queue_id, xsk_fd) in queue_fds {
660 if xsk_fd >= 0 {
661 if let Err(e) = bpf_map_update_xsk(map_fd, queue_id, xsk_fd) {
662 return match e.raw_os_error() {
663 Some(libc::EPERM | libc::EACCES) => {
664 Err(BpfFilterStatus::FallbackUnprivileged)
665 }
666 _ => Err(BpfFilterStatus::Unsupported),
667 };
668 }
669 } else {
670 return Err(BpfFilterStatus::Unsupported);
672 }
673 }
674
675 let prog_fd = match bpf_prog_load_xdp(listen_port, map_fd) {
677 Ok(fd) => fd,
678 Err(e) => {
679 return match e.raw_os_error() {
680 Some(libc::EPERM | libc::EACCES) => Err(BpfFilterStatus::FallbackUnprivileged),
681 _ => Err(BpfFilterStatus::Unsupported),
682 };
683 }
684 };
685 let prog_guard = AutoCloseFd(prog_fd);
686
687 if let Err(e) = bpf_attach_xdp(ifindex, prog_fd) {
689 return match e.raw_os_error() {
690 Some(libc::EPERM | libc::EACCES) => Err(BpfFilterStatus::FallbackUnprivileged),
691 _ => Err(BpfFilterStatus::Unsupported),
692 };
693 }
694
695 let map_fd = map_guard.into_raw();
696 let prog_fd = prog_guard.into_raw();
697 Ok(Self { map_fd, prog_fd })
698 }
699
700 pub fn load_and_attach(
702 ifname: &str,
703 listen_port: u16,
704 queue_id: u32,
705 xsk_fd: RawFd,
706 ) -> Result<Self, BpfFilterStatus> {
707 Self::load_multi_queue(ifname, listen_port, &[(queue_id, xsk_fd)])
708 }
709
710 pub fn attach_multi_queue(
714 ifname: &str,
715 listen_port: u16,
716 queue_fds: &[(u32, RawFd)],
717 ) -> BpfFilterStatus {
718 match Self::load_multi_queue(ifname, listen_port, queue_fds) {
719 Ok(filter) => {
720 let (map_fd, prog_fd) = filter.into_raw();
721 if map_fd >= 0 {
722 unsafe {
724 libc::close(map_fd);
725 }
726 }
727 BpfFilterStatus::Attached(prog_fd)
728 }
729 Err(status) => status,
730 }
731 }
732
733 pub fn attach_opportunistic(
737 ifname: &str,
738 listen_port: u16,
739 queue_id: u32,
740 xsk_fd: RawFd,
741 ) -> BpfFilterStatus {
742 Self::attach_multi_queue(ifname, listen_port, &[(queue_id, xsk_fd)])
743 }
744}
745
746impl Drop for XdpRedirectFilter {
747 fn drop(&mut self) {
748 if self.map_fd >= 0 {
749 unsafe {
752 libc::close(self.map_fd);
753 }
754 self.map_fd = -1;
755 }
756 if self.prog_fd >= 0 {
757 unsafe {
760 libc::close(self.prog_fd);
761 }
762 self.prog_fd = -1;
763 }
764 }
765}