1pub mod bindings;
51
52use crate::bindings::*;
53use core::ffi::{c_int, c_uint, c_void};
54use std::fmt::{Debug, Formatter};
55use std::io;
56use std::net::{Ipv4Addr, Ipv6Addr};
57use std::ops::BitOr;
58use std::os::fd::{AsRawFd, RawFd};
59
60trait IsMinusOne {
61 fn is_minus_one(&self) -> bool;
62}
63
64macro_rules! impl_is_minus_one {
65 ($($t:ident)*) => ($(impl IsMinusOne for $t {
66 fn is_minus_one(&self) -> bool {
67 *self == -1
68 }
69 })*)
70}
71
72impl_is_minus_one! { i8 i16 i32 i64 isize }
73
74fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
75 if t.is_minus_one() {
76 Err(io::Error::last_os_error())
77 } else {
78 Ok(t)
79 }
80}
81
82#[derive(Copy, Clone)]
83pub struct Msg<'a>(&'a nlmsghdr);
84
85impl<'a> Msg<'a> {
86 unsafe fn from(nlh: *const nlmsghdr, len: c_int) -> Option<Self> {
87 unsafe {
88 if !mnl_nlmsg_ok(nlh, len) {
89 return None;
90 }
91 Some(Self(&*nlh))
92 }
93 }
94
95 fn next(&self, len: &mut c_int) -> Option<Self> {
96 unsafe {
97 let next = mnl_nlmsg_next(self.0, len as _);
98 Self::from(next, *len)
99 }
100 }
101
102 pub fn as_packet_msg(&self) -> Option<PacketMsg<'a>> {
103 if self.0.nlmsg_flags as u8 != NFQNL_MSG_PACKET as u8 {
104 return None;
105 }
106 let mut tb = [None; NFQA_MAX as usize + 1];
107 if unsafe {
108 mnl_attr_parse(
109 self.0 as _,
110 size_of::<nfgenmsg>() as c_uint,
111 Some(parse_attr_cb),
112 tb.as_mut_ptr() as *mut c_void,
113 )
114 } < 0
115 {
116 return None;
117 }
118 Some(PacketMsg(tb))
119 }
120
121 pub fn as_bytes(&self) -> &'a [u8] {
122 unsafe {
123 std::slice::from_raw_parts(self.0 as *const _ as *const u8, self.0.nlmsg_len as usize)
124 }
125 }
126}
127
128extern "C" fn parse_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
129 let tb = data as *mut Option<&nlattr>;
130 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
131 if unsafe { mnl_attr_type_valid(attr, NFQA_MAX as u16) } < 0 {
132 return MNL_CB_OK as c_int;
133 }
134 match attr_type {
135 NFQA_MARK
136 | NFQA_IFINDEX_INDEV
137 | NFQA_IFINDEX_OUTDEV
138 | NFQA_IFINDEX_PHYSINDEV
139 | NFQA_IFINDEX_PHYSOUTDEV
140 | NFQA_CT_INFO
141 | NFQA_CAP_LEN
142 | NFQA_SKB_INFO
143 | NFQA_UID
144 | NFQA_GID
145 | NFQA_PRIORITY
146 | NFQA_CGROUP_CLASSID => {
147 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U32) } < 0 {
148 return MNL_CB_ERROR;
149 }
150 }
151 NFQA_PACKET_HDR => {
152 if unsafe {
153 mnl_attr_validate2(attr, MNL_TYPE_UNSPEC, size_of::<nfqnl_msg_packet_hdr>())
154 } < 0
155 {
156 return MNL_CB_ERROR;
157 }
158 }
159 NFQA_TIMESTAMP => {
160 if unsafe {
161 mnl_attr_validate2(
162 attr,
163 MNL_TYPE_UNSPEC,
164 size_of::<nfqnl_msg_packet_timestamp>(),
165 )
166 } < 0
167 {
168 return MNL_CB_ERROR;
169 }
170 }
171 NFQA_HWADDR => {
172 if unsafe {
173 mnl_attr_validate2(attr, MNL_TYPE_UNSPEC, size_of::<nfqnl_msg_packet_hw>())
174 } < 0
175 {
176 return MNL_CB_ERROR;
177 }
178 }
179 NFQA_CT | NFQA_VLAN => {
180 if unsafe { mnl_attr_validate(attr, MNL_TYPE_NESTED) } < 0 {
181 return MNL_CB_ERROR;
182 }
183 }
184 NFQA_PAYLOAD | NFQA_SECCTX | NFQA_L2HDR => {}
185 _ => {}
186 }
187 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
188 MNL_CB_OK as c_int
189}
190
191#[derive(Copy, Clone)]
192pub struct Messages<'a>(c_int, Option<Msg<'a>>);
193
194impl Messages<'_> {
195 pub fn new(buf: &[u8]) -> Self {
196 let len = buf.len() as c_int;
197 Self(len, unsafe { Msg::from(buf.as_ptr() as _, len) })
198 }
199}
200
201impl<'a> Iterator for Messages<'a> {
202 type Item = Msg<'a>;
203
204 fn next(&mut self) -> Option<Self::Item> {
205 let next = self.1.as_ref().and_then(|msg| msg.next(&mut self.0));
206 let msg = self.1.take();
207 self.1 = next;
208 msg
209 }
210}
211
212pub const SOCKET_BUFFER_SIZE: usize = 8192;
213
214#[derive(Copy, Clone)]
215pub struct Socket<'a>(&'a mnl_socket);
216
217impl Socket<'_> {
218 pub fn new(flags: i32) -> io::Result<Self> {
219 unsafe {
220 let socket = mnl_socket_open2(NETLINK_NETFILTER as c_int, flags);
221 if socket.is_null() {
222 return Err(io::Error::last_os_error());
223 }
224 let mut ret: c_int = 1;
225 cvt(mnl_socket_setsockopt(
226 socket as _,
227 NETLINK_NO_ENOBUFS as c_int,
228 &mut ret as *mut _ as _,
229 size_of::<c_int>() as _,
230 ))?;
231 cvt(mnl_socket_bind(socket, 0, MNL_SOCKET_AUTOPID as _))?;
232 Ok(Self(&*socket))
233 }
234 }
235
236 pub fn recv(&self, buf: &mut [u8]) -> io::Result<Messages<'_>> {
237 unsafe {
238 let ret = cvt(mnl_socket_recvfrom(
239 self.0,
240 buf.as_mut_ptr() as _,
241 buf.len(),
242 ))? as usize;
243 if mnl_cb_run(
244 buf.as_ptr() as _,
245 ret,
246 0,
247 mnl_socket_get_portid(self.0),
248 None,
249 std::ptr::null_mut(),
250 ) < 0
251 {
252 return Err(io::Error::last_os_error());
253 }
254 Ok(Messages::new(&buf[..ret]))
255 }
256 }
257
258 pub fn send(&self, msg: Msg) -> io::Result<()> {
259 cvt(unsafe {
260 mnl_socket_sendto(self.0, msg.as_bytes().as_ptr() as _, msg.as_bytes().len())
261 })?;
262 Ok(())
263 }
264}
265
266impl AsRawFd for Socket<'_> {
267 fn as_raw_fd(&self) -> RawFd {
268 unsafe { mnl_socket_get_fd(self.0) }
269 }
270}
271
272#[derive(Debug, Clone, Copy)]
273#[repr(u8)]
274pub enum CopyMode {
275 Meta = NFQNL_COPY_META as u8,
276 Packet = NFQNL_COPY_PACKET as u8,
277}
278
279#[derive(Debug, Clone, Copy)]
280#[repr(u8)]
281pub enum CmdType {
282 None = NFQNL_CFG_CMD_NONE as u8,
283 Bind = NFQNL_CFG_CMD_BIND as u8,
284 Unbind = NFQNL_CFG_CMD_UNBIND as u8,
285}
286
287#[derive(Debug, Clone, Copy)]
288#[repr(u32)]
289pub enum CfgFlag {
290 FailOpen = NFQA_CFG_F_FAIL_OPEN,
292 ConnTrack = NFQA_CFG_F_CONNTRACK,
293 Gso = NFQA_CFG_F_GSO,
295 UidGid = NFQA_CFG_F_UID_GID,
296 SecCtx = NFQA_CFG_F_SECCTX,
297}
298
299impl BitOr for CfgFlag {
300 type Output = u32;
301
302 fn bitor(self, rhs: Self) -> Self::Output {
303 (self as u32) | (rhs as u32)
304 }
305}
306
307impl BitOr<CfgFlag> for u32 {
308 type Output = u32;
309
310 fn bitor(self, rhs: CfgFlag) -> Self::Output {
311 self | (rhs as u32)
312 }
313}
314
315pub enum VerdictType {
316 Drop,
317 Accept,
318 Queue(u32),
319 Repeat,
320}
321
322pub struct MsgBuilder<'a>(&'a mut nlmsghdr);
323
324impl<'a> MsgBuilder<'a> {
325 pub fn new(buf: &'a mut [u8], queue_num: u16) -> Self {
326 unsafe {
327 let nlh = mnl_nlmsg_put_header(buf.as_mut_ptr() as _);
328 (*nlh).nlmsg_type = (NFNL_SUBSYS_QUEUE as u16) << 8;
329 (*nlh).nlmsg_flags = NLM_F_REQUEST as u16;
330 let nfg = mnl_nlmsg_put_extra_header(nlh, size_of::<nfgenmsg>()) as *mut nfgenmsg;
331 (*nfg).nfgen_family = 0;
332 (*nfg).version = NFNETLINK_V0 as u8;
333 (*nfg).res_id = queue_num.to_be();
334 Self(&mut *nlh)
335 }
336 }
337
338 pub fn ack(self) -> Self {
339 self.0.nlmsg_flags |= NLM_F_ACK as u16;
340 self
341 }
342
343 pub fn cfg(self) -> CfgMsgBuilder<'a> {
344 self.0.nlmsg_type |= NFQNL_MSG_CONFIG as u16;
345 CfgMsgBuilder(self)
346 }
347
348 pub fn verdict(self) -> VerdictMsgBuilder<'a> {
349 self.0.nlmsg_type |= NFQNL_MSG_VERDICT as u16;
350 VerdictMsgBuilder(self)
351 }
352
353 pub fn build(self) -> Msg<'a> {
354 Msg(self.0)
355 }
356}
357
358pub struct CfgMsgBuilder<'a>(MsgBuilder<'a>);
359
360impl<'a> CfgMsgBuilder<'a> {
361 pub fn cmd(self, cmd: CmdType) -> Self {
362 unsafe {
363 mnl_attr_put(
364 self.0.0,
365 NFQA_CFG_CMD as u16,
366 size_of::<nfqnl_msg_config_cmd>(),
367 &nfqnl_msg_config_cmd {
368 command: cmd as u8,
369 _pad: 0,
370 pf: 0,
371 } as *const _ as _,
372 );
373 }
374 self
375 }
376
377 pub fn params(self, copy_range: u32, copy_mode: CopyMode) -> Self {
378 unsafe {
379 mnl_attr_put(
380 self.0.0,
381 NFQA_CFG_PARAMS as u16,
382 size_of::<nfqnl_msg_config_params>(),
383 &nfqnl_msg_config_params {
384 copy_range: copy_range.to_be(),
385 copy_mode: copy_mode as u8,
386 } as *const _ as _,
387 );
388 }
389 self
390 }
391
392 pub fn queue_maxlen(self, queue_maxlen: u32) -> Self {
393 unsafe {
394 mnl_attr_put_u32(self.0.0, NFQA_CFG_QUEUE_MAXLEN as u16, queue_maxlen.to_be());
395 }
396 self
397 }
398
399 pub fn flags(self, flags: u32) -> Self {
400 unsafe {
401 mnl_attr_put_u32(self.0.0, NFQA_CFG_FLAGS as u16, flags.to_be());
402 mnl_attr_put_u32(self.0.0, NFQA_CFG_MASK as u16, flags.to_be());
403 }
404 self
405 }
406
407 pub fn build(self) -> Msg<'a> {
408 self.0.build()
409 }
410}
411
412pub struct VerdictMsgBuilder<'a>(MsgBuilder<'a>);
413
414impl<'a> VerdictMsgBuilder<'a> {
415 pub fn verdict_hdr(self, id: u32, verdict: VerdictType) -> Self {
416 unsafe {
417 mnl_attr_put(
418 self.0.0,
419 NFQA_VERDICT_HDR as u16,
420 size_of::<nfqnl_msg_verdict_hdr>(),
421 &nfqnl_msg_verdict_hdr {
422 verdict: match verdict {
423 VerdictType::Drop => NF_DROP,
424 VerdictType::Accept => NF_ACCEPT,
425 VerdictType::Queue(num) => ((num << 16) & NF_VERDICT_QMASK) | NF_QUEUE,
426 VerdictType::Repeat => NF_REPEAT,
427 }
428 .to_be(),
429 id: id.to_be(),
430 } as *const _ as _,
431 );
432 }
433 self
434 }
435
436 pub fn ct(self) -> VerdictCtBuilder<'a> {
437 unsafe {
438 let start = mnl_attr_nest_start(self.0.0, NFQA_CT as u16);
439 VerdictCtBuilder(self.0, start)
440 }
441 }
442
443 pub fn payload(self, payload: &[u8]) -> Self {
444 unsafe {
445 mnl_attr_put(
446 self.0.0,
447 NFQA_PAYLOAD as u16,
448 payload.len(),
449 &payload as *const _ as _,
450 )
451 }
452 self
453 }
454
455 pub fn mark(self, mark: u32) -> Self {
456 unsafe {
457 mnl_attr_put_u32(self.0.0, NFQA_MARK as u16, mark.to_be());
458 }
459 self
460 }
461
462 pub fn priority(self, priority: u32) -> Self {
463 unsafe {
464 mnl_attr_put_u32(self.0.0, NFQA_PRIORITY as u16, priority.to_be());
465 }
466 self
467 }
468
469 pub fn build(self) -> Msg<'a> {
470 self.0.build()
471 }
472}
473
474pub struct VerdictCtBuilder<'a>(MsgBuilder<'a>, *mut nlattr);
475
476impl<'a> VerdictCtBuilder<'a> {
477 pub fn timeout(self, timeout: u32) -> Self {
478 unsafe {
479 mnl_attr_put_u32(self.0.0, CTA_TIMEOUT as u16, timeout.to_be());
480 }
481 self
482 }
483
484 pub fn status(self, status: u32) -> Self {
485 unsafe {
486 mnl_attr_put_u32(self.0.0, CTA_STATUS as u16, status.to_be());
487 }
488 self
489 }
490
491 pub fn mark(self, mark: u32) -> Self {
492 unsafe {
493 mnl_attr_put_u32(self.0.0, CTA_MARK as u16, mark.to_be());
494 }
495 self
496 }
497
498 pub fn build(self) -> Msg<'a> {
499 unsafe { mnl_attr_nest_end(self.0.0, self.1) }
500 self.0.build()
501 }
502}
503
504#[derive(Debug, Copy, Clone)]
505pub struct PacketHdr {
506 pub packet_id: u32,
507 pub hw_protocol: u16,
509 pub hook: u8,
511}
512
513#[derive(Debug, Copy, Clone)]
514pub struct Timestamp {
515 pub sec: u64,
516 pub usec: u64,
517}
518
519#[derive(Debug, Copy, Clone)]
520pub struct HwAddr {
521 pub hw_addrlen: u16,
522 pub hw_addr: [u8; 8],
523}
524
525#[derive(Copy, Clone)]
526pub struct PacketMsg<'a>([Option<&'a nlattr>; NFQA_MAX as usize + 1]);
527
528impl<'a> PacketMsg<'a> {
529 pub fn packet_hdr(&self) -> Option<PacketHdr> {
530 self.0[NFQA_PACKET_HDR as usize].map(|nla| unsafe {
531 let packet_hdr = mnl_attr_get_payload(nla) as *mut nfqnl_msg_packet_hdr;
532 PacketHdr {
533 packet_id: u32::from_be((*packet_hdr).packet_id),
534 hw_protocol: u16::from_be((*packet_hdr).hw_protocol),
535 hook: (*packet_hdr).hook,
536 }
537 })
538 }
539
540 pub fn mark(&self) -> Option<u32> {
541 self.0[NFQA_MARK as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
542 }
543
544 pub fn timestamp(&self) -> Option<Timestamp> {
545 self.0[NFQA_TIMESTAMP as usize].map(|nla| unsafe {
546 let timestamp = std::ptr::read_unaligned(
547 mnl_attr_get_payload(nla) as *mut nfqnl_msg_packet_timestamp
548 );
549 Timestamp {
550 sec: u64::from_be(timestamp.sec),
551 usec: u64::from_be(timestamp.usec),
552 }
553 })
554 }
555
556 pub fn indev(&self) -> Option<u32> {
557 self.0[NFQA_IFINDEX_INDEV as usize]
558 .map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
559 }
560
561 pub fn outdev(&self) -> Option<u32> {
562 self.0[NFQA_IFINDEX_OUTDEV as usize]
563 .map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
564 }
565
566 pub fn phys_indev(&self) -> Option<u32> {
567 self.0[NFQA_IFINDEX_PHYSINDEV as usize]
568 .map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
569 }
570
571 pub fn phys_outdev(&self) -> Option<u32> {
572 self.0[NFQA_IFINDEX_PHYSOUTDEV as usize]
573 .map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
574 }
575
576 pub fn hw_addr(&self) -> Option<HwAddr> {
577 self.0[NFQA_HWADDR as usize].map(|nla| unsafe {
578 let hw_addr = mnl_attr_get_payload(nla) as *mut nfqnl_msg_packet_hw;
579 HwAddr {
580 hw_addrlen: u16::from_be((*hw_addr).hw_addrlen),
581 hw_addr: (*hw_addr).hw_addr,
582 }
583 })
584 }
585
586 pub fn payload(&self) -> Option<&'a [u8]> {
587 self.0[NFQA_PAYLOAD as usize].map(|nla| unsafe {
588 std::slice::from_raw_parts(
589 mnl_attr_get_payload(nla) as *const u8,
590 mnl_attr_get_payload_len(nla) as usize,
591 )
592 })
593 }
594
595 pub fn ct(&self) -> Option<Ct<'a>> {
596 self.0[NFQA_CT as usize].map(|nla| {
597 let mut tb = [None; CTA_MAX as usize + 1];
598 if unsafe {
599 mnl_attr_parse_nested(nla, Some(parse_ct_attr_cb), tb.as_mut_ptr() as *mut c_void)
600 } < 0
601 {
602 return None;
603 }
604 Some(Ct(tb))
605 })?
606 }
607
608 pub fn ct_info(&self) -> Option<u32> {
609 self.0[NFQA_CT_INFO as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
610 }
611
612 pub fn cap_len(&self) -> Option<u32> {
613 self.0[NFQA_CAP_LEN as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
614 }
615
616 pub fn skb_info(&self) -> Option<u32> {
617 self.0[NFQA_SKB_INFO as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
618 }
619
620 pub fn uid(&self) -> Option<u32> {
621 self.0[NFQA_UID as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
622 }
623
624 pub fn gid(&self) -> Option<u32> {
625 self.0[NFQA_GID as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
626 }
627
628 pub fn secctx(&self) -> Option<&'a [u8]> {
629 self.0[NFQA_SECCTX as usize].map(|nla| unsafe {
630 std::slice::from_raw_parts(
631 mnl_attr_get_payload(nla) as *const u8,
632 mnl_attr_get_payload_len(nla) as usize,
633 )
634 })
635 }
636
637 pub fn vlan(&self) -> Option<Vlan<'a>> {
638 self.0[NFQA_VLAN as usize].map(|nla| {
639 let mut tb = [None; NFQA_VLAN_MAX as usize + 1];
640 if unsafe {
641 mnl_attr_parse_nested(
642 nla,
643 Some(parse_vlan_attr_cb),
644 tb.as_mut_ptr() as *mut c_void,
645 )
646 } < 0
647 {
648 return None;
649 }
650 Some(Vlan(tb))
651 })?
652 }
653
654 pub fn l2hdr(&self) -> Option<&'a [u8]> {
655 self.0[NFQA_L2HDR as usize].map(|nla| unsafe {
656 std::slice::from_raw_parts(
657 mnl_attr_get_payload(nla) as *const u8,
658 mnl_attr_get_payload_len(nla) as usize,
659 )
660 })
661 }
662
663 pub fn priority(&self) -> Option<u32> {
664 self.0[NFQA_PRIORITY as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
665 }
666
667 pub fn cgroup_classid(&self) -> Option<u32> {
668 self.0[NFQA_CGROUP_CLASSID as usize]
669 .map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
670 }
671}
672
673impl Debug for PacketMsg<'_> {
674 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
675 f.debug_struct("PacketMsg")
676 .field("packet_hdr", &self.packet_hdr())
677 .field("mark", &self.mark())
678 .field("timestamp", &self.timestamp())
679 .field("indev", &self.indev())
680 .field("outdev", &self.outdev())
681 .field("phys_indev", &self.phys_indev())
682 .field("phys_outdev", &self.phys_outdev())
683 .field("hw_addr", &self.hw_addr())
684 .field("payload", &self.payload())
685 .field("ct", &self.ct())
686 .field("ct_info", &self.ct_info())
687 .field("cap_len", &self.cap_len())
688 .field("skb_info", &self.skb_info())
689 .field("uid", &self.uid())
690 .field("gid", &self.gid())
691 .field("secctx", &self.secctx())
692 .field("vlan", &self.vlan())
693 .field("l2hdr", &self.l2hdr())
694 .field("priority", &self.priority())
695 .field("cgroup_classid", &self.cgroup_classid())
696 .finish()
697 }
698}
699
700extern "C" fn parse_ct_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
701 let tb = data as *mut Option<&nlattr>;
702 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
703 if unsafe { mnl_attr_type_valid(attr, CTA_MAX as u16) } < 0 {
704 return MNL_CB_OK as c_int;
705 }
706 match attr_type {
707 CTA_ZONE => {
708 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U16) } < 0 {
709 return MNL_CB_ERROR;
710 }
711 }
712 CTA_STATUS | CTA_TIMEOUT | CTA_MARK | CTA_USE | CTA_ID => {
713 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U32) } < 0 {
714 return MNL_CB_ERROR;
715 }
716 }
717 CTA_TUPLE_ORIG | CTA_TUPLE_REPLY | CTA_TUPLE_MASTER | CTA_PROTOINFO | CTA_COUNTERS_ORIG
718 | CTA_COUNTERS_REPLY | CTA_TIMESTAMP => {
719 if unsafe { mnl_attr_validate(attr, MNL_TYPE_NESTED) } < 0 {
720 return MNL_CB_ERROR;
721 }
722 }
723 _ => {}
724 }
725 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
726 MNL_CB_OK as c_int
727}
728
729extern "C" fn parse_vlan_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
730 let tb = data as *mut Option<&nlattr>;
731 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
732 if unsafe { mnl_attr_type_valid(attr, NFQA_VLAN_MAX as u16) } < 0 {
733 return MNL_CB_OK as c_int;
734 }
735 match attr_type {
736 NFQA_VLAN_PROTO | NFQA_VLAN_TCI => {
737 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U16) } < 0 {
738 return MNL_CB_ERROR;
739 }
740 }
741 _ => {}
742 }
743 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
744 MNL_CB_OK as c_int
745}
746
747#[derive(Copy, Clone)]
748pub struct Vlan<'a>([Option<&'a nlattr>; NFQA_VLAN_MAX as usize + 1]);
749
750impl Vlan<'_> {
751 pub fn proto(&self) -> Option<u16> {
752 self.0[NFQA_VLAN_PROTO as usize].map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
753 }
754
755 pub fn tci(&self) -> Option<u16> {
756 self.0[NFQA_VLAN_TCI as usize].map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
757 }
758}
759
760impl Debug for Vlan<'_> {
761 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
762 f.debug_struct("Vlan")
763 .field("proto", &self.proto())
764 .field("tci", &self.tci())
765 .finish()
766 }
767}
768
769#[derive(Copy, Clone)]
770pub struct Ct<'a>([Option<&'a nlattr>; CTA_MAX as usize + 1]);
771
772impl<'a> Ct<'a> {
773 fn tuple(&self, tuple: usize) -> Option<CtTuple<'a>> {
774 self.0[tuple].map(|nla| {
775 let mut tb = [None; CTA_TUPLE_MAX as usize + 1];
776 if unsafe {
777 mnl_attr_parse_nested(
778 nla,
779 Some(parse_tuple_attr_cb),
780 tb.as_mut_ptr() as *mut c_void,
781 )
782 } < 0
783 {
784 return None;
785 }
786 Some(CtTuple(tb))
787 })?
788 }
789
790 pub fn tuple_orig(&self) -> Option<CtTuple<'a>> {
791 self.tuple(CTA_TUPLE_ORIG as usize)
792 }
793
794 pub fn tuple_reply(&self) -> Option<CtTuple<'a>> {
795 self.tuple(CTA_TUPLE_REPLY as usize)
796 }
797
798 pub fn tuple_master(&self) -> Option<CtTuple<'a>> {
799 self.tuple(CTA_TUPLE_MASTER as usize)
800 }
801
802 pub fn status(&self) -> Option<u32> {
803 self.0[CTA_STATUS as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
804 }
805
806 pub fn protoinfo(&self) -> Option<CtProtoInfo<'a>> {
807 self.0[CTA_PROTOINFO as usize].map(|nla| {
808 let mut tb = [None; CTA_PROTOINFO_MAX as usize + 1];
809 if unsafe {
810 mnl_attr_parse_nested(
811 nla,
812 Some(parse_protoinfo_attr_cb),
813 tb.as_mut_ptr() as *mut c_void,
814 )
815 } < 0
816 {
817 return None;
818 }
819 Some(CtProtoInfo(tb))
820 })?
821 }
822
823 fn counters(&self, counters: usize) -> Option<CtCounters<'a>> {
824 self.0[counters].map(|nla| {
825 let mut tb = [None; CTA_COUNTERS_MAX as usize + 1];
826 if unsafe {
827 mnl_attr_parse_nested(
828 nla,
829 Some(parse_counters_attr_cb),
830 tb.as_mut_ptr() as *mut c_void,
831 )
832 } < 0
833 {
834 return None;
835 }
836 Some(CtCounters(tb))
837 })?
838 }
839
840 pub fn counters_orig(&self) -> Option<CtCounters<'a>> {
841 self.counters(CTA_COUNTERS_ORIG as usize)
842 }
843
844 pub fn counters_reply(&self) -> Option<CtCounters<'a>> {
845 self.counters(CTA_COUNTERS_REPLY as usize)
846 }
847
848 pub fn timeout(&self) -> Option<u32> {
849 self.0[CTA_TIMEOUT as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
850 }
851
852 pub fn mark(&self) -> Option<u32> {
853 self.0[CTA_MARK as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
854 }
855
856 pub fn r#use(&self) -> Option<u32> {
857 self.0[CTA_USE as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
858 }
859
860 pub fn id(&self) -> Option<u32> {
861 self.0[CTA_ID as usize].map(|nla| u32::from_be(unsafe { mnl_attr_get_u32(nla) }))
862 }
863
864 pub fn zone(&self) -> Option<u16> {
865 self.0[CTA_ZONE as usize].map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
866 }
867
868 pub fn timestamp(&self) -> Option<CtTimestamp<'a>> {
869 self.0[CTA_TIMESTAMP as usize].map(|nla| {
870 let mut tb = [None; CTA_TIMESTAMP_MAX as usize + 1];
871 if unsafe {
872 mnl_attr_parse_nested(
873 nla,
874 Some(parse_timestamp_attr_cb),
875 tb.as_mut_ptr() as *mut c_void,
876 )
877 } < 0
878 {
879 return None;
880 }
881 Some(CtTimestamp(tb))
882 })?
883 }
884}
885
886impl Debug for Ct<'_> {
887 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
888 f.debug_struct("Ct")
889 .field("tuple_orig", &self.tuple_orig())
890 .field("tuple_reply", &self.tuple_reply())
891 .field("tuple_master", &self.tuple_master())
892 .field("status", &self.status())
893 .field("protoinfo", &self.protoinfo())
894 .field("counters_orig", &self.counters_orig())
895 .field("counters_reply", &self.counters_reply())
896 .field("timeout", &self.timeout())
897 .field("mark", &self.mark())
898 .field("use", &self.r#use())
899 .field("id", &self.id())
900 .field("zone", &self.zone())
901 .field("timestamp", &self.timestamp())
902 .finish()
903 }
904}
905
906extern "C" fn parse_tuple_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
907 let tb = data as *mut Option<&nlattr>;
908 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
909 if unsafe { mnl_attr_type_valid(attr, CTA_TUPLE_MAX as u16) } < 0 {
910 return MNL_CB_OK as c_int;
911 }
912 match attr_type {
913 CTA_TUPLE_IP | CTA_TUPLE_PROTO => {
914 if unsafe { mnl_attr_validate(attr, MNL_TYPE_NESTED) } < 0 {
915 return MNL_CB_ERROR;
916 }
917 }
918 CTA_TUPLE_ZONE => {
919 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U16) } < 0 {
920 return MNL_CB_ERROR;
921 }
922 }
923 _ => {}
924 }
925 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
926 MNL_CB_OK as c_int
927}
928
929extern "C" fn parse_protoinfo_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
930 let tb = data as *mut Option<&nlattr>;
931 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
932 if unsafe { mnl_attr_type_valid(attr, CTA_PROTOINFO_MAX as u16) } < 0 {
933 return MNL_CB_OK as c_int;
934 }
935 match attr_type {
936 CTA_PROTOINFO_TCP => {
937 if unsafe { mnl_attr_validate(attr, MNL_TYPE_NESTED) } < 0 {
938 return MNL_CB_ERROR;
939 }
940 }
941 CTA_PROTOINFO_DCCP | CTA_PROTOINFO_SCTP => {}
942 _ => {}
943 }
944 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
945 MNL_CB_OK as c_int
946}
947
948extern "C" fn parse_counters_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
949 let tb = data as *mut Option<&nlattr>;
950 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
951 if unsafe { mnl_attr_type_valid(attr, CTA_COUNTERS_MAX as u16) } < 0 {
952 return MNL_CB_OK as c_int;
953 }
954 match attr_type {
955 CTA_COUNTERS_PACKETS | CTA_COUNTERS_BYTES => {
956 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U64) } < 0 {
957 return MNL_CB_ERROR;
958 }
959 }
960 _ => {}
961 }
962 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
963 MNL_CB_OK as c_int
964}
965
966extern "C" fn parse_timestamp_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
967 let tb = data as *mut Option<&nlattr>;
968 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
969 if unsafe { mnl_attr_type_valid(attr, CTA_TIMESTAMP_MAX as u16) } < 0 {
970 return MNL_CB_OK as c_int;
971 }
972 match attr_type {
973 CTA_TIMESTAMP_START | CTA_TIMESTAMP_STOP => {
974 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U64) } < 0 {
975 return MNL_CB_ERROR;
976 }
977 }
978 _ => {}
979 }
980 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
981 MNL_CB_OK as c_int
982}
983
984#[derive(Copy, Clone)]
985pub struct CtTuple<'a>([Option<&'a nlattr>; CTA_TUPLE_MAX as usize + 1]);
986
987impl<'a> CtTuple<'a> {
988 pub fn ip(&self) -> Option<CtTupleIp<'a>> {
989 self.0[CTA_TUPLE_IP as usize].map(|nla| {
990 let mut tb = [None; CTA_IP_MAX as usize + 1];
991 if unsafe {
992 mnl_attr_parse_nested(nla, Some(parse_ip_attr_cb), tb.as_mut_ptr() as *mut c_void)
993 } < 0
994 {
995 return None;
996 }
997 Some(CtTupleIp(tb))
998 })?
999 }
1000
1001 pub fn proto(&self) -> Option<CtTupleProto<'a>> {
1002 self.0[CTA_TUPLE_PROTO as usize].map(|nla| {
1003 let mut tb = [None; CTA_PROTO_MAX as usize + 1];
1004 if unsafe {
1005 mnl_attr_parse_nested(
1006 nla,
1007 Some(parse_proto_attr_cb),
1008 tb.as_mut_ptr() as *mut c_void,
1009 )
1010 } < 0
1011 {
1012 return None;
1013 }
1014 Some(CtTupleProto(tb))
1015 })?
1016 }
1017
1018 pub fn zone(&self) -> Option<u16> {
1019 self.0[CTA_TUPLE_ZONE as usize].map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
1020 }
1021}
1022
1023impl Debug for CtTuple<'_> {
1024 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1025 f.debug_struct("CtTuple")
1026 .field("ip", &self.ip())
1027 .field("proto", &self.proto())
1028 .field("zone", &self.zone())
1029 .finish()
1030 }
1031}
1032
1033extern "C" fn parse_ip_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
1034 let tb = data as *mut Option<&nlattr>;
1035 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
1036 if unsafe { mnl_attr_type_valid(attr, CTA_IP_MAX as u16) } < 0 {
1037 return MNL_CB_OK as c_int;
1038 }
1039 match attr_type {
1040 CTA_IP_V4_SRC | CTA_IP_V4_DST => {
1041 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U32) } < 0 {
1042 return MNL_CB_ERROR;
1043 }
1044 }
1045 CTA_IP_V6_SRC | CTA_IP_V6_DST => {
1046 if unsafe { mnl_attr_validate2(attr, MNL_TYPE_UNSPEC, 16) } < 0 {
1047 return MNL_CB_ERROR;
1048 }
1049 }
1050 _ => {}
1051 }
1052 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
1053 MNL_CB_OK as c_int
1054}
1055
1056extern "C" fn parse_proto_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
1057 let tb = data as *mut Option<&nlattr>;
1058 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
1059 if unsafe { mnl_attr_type_valid(attr, CTA_PROTO_MAX as u16) } < 0 {
1060 return MNL_CB_OK as c_int;
1061 }
1062 match attr_type {
1063 CTA_PROTO_NUM
1064 | CTA_PROTO_ICMP_TYPE
1065 | CTA_PROTO_ICMP_CODE
1066 | CTA_PROTO_ICMPV6_TYPE
1067 | CTA_PROTO_ICMPV6_CODE => {
1068 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U8) } < 0 {
1069 return MNL_CB_ERROR;
1070 }
1071 }
1072 CTA_PROTO_SRC_PORT | CTA_PROTO_DST_PORT | CTA_PROTO_ICMP_ID | CTA_PROTO_ICMPV6_ID => {
1073 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U16) } < 0 {
1074 return MNL_CB_ERROR;
1075 }
1076 }
1077 _ => {}
1078 }
1079 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
1080 MNL_CB_OK as c_int
1081}
1082
1083#[derive(Copy, Clone)]
1084pub struct CtTupleIp<'a>([Option<&'a nlattr>; CTA_IP_MAX as usize + 1]);
1085
1086impl CtTupleIp<'_> {
1087 pub fn ipv4_src(&self) -> Option<Ipv4Addr> {
1088 self.0[CTA_IP_V4_SRC as usize]
1089 .map(|nla| Ipv4Addr::from(u32::from_be(unsafe { mnl_attr_get_u32(nla) })))
1090 }
1091
1092 pub fn ipv4_dst(&self) -> Option<Ipv4Addr> {
1093 self.0[CTA_IP_V4_DST as usize]
1094 .map(|nla| Ipv4Addr::from(u32::from_be(unsafe { mnl_attr_get_u32(nla) })))
1095 }
1096
1097 fn ipv6(&self, r#type: usize) -> Option<Ipv6Addr> {
1098 self.0[r#type].map(|nla| {
1099 let mut bytes = [0u8; 16];
1100 unsafe {
1101 std::ptr::copy_nonoverlapping(
1102 mnl_attr_get_payload(nla) as *const u8,
1103 bytes.as_mut_ptr(),
1104 16,
1105 );
1106 }
1107 Ipv6Addr::from(bytes)
1108 })
1109 }
1110
1111 pub fn ipv6_src(&self) -> Option<Ipv6Addr> {
1112 self.ipv6(CTA_IP_V6_SRC as usize)
1113 }
1114
1115 pub fn ipv6_dst(&self) -> Option<Ipv6Addr> {
1116 self.ipv6(CTA_IP_V6_DST as usize)
1117 }
1118}
1119
1120impl Debug for CtTupleIp<'_> {
1121 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1122 f.debug_struct("CtTupleIp")
1123 .field("ipv4_src", &self.ipv4_src())
1124 .field("ipv4_dst", &self.ipv4_dst())
1125 .field("ipv6_src", &self.ipv6_src())
1126 .field("ipv6_dst", &self.ipv6_dst())
1127 .finish()
1128 }
1129}
1130
1131#[derive(Copy, Clone)]
1132pub struct CtTupleProto<'a>([Option<&'a nlattr>; CTA_PROTO_MAX as usize + 1]);
1133
1134impl CtTupleProto<'_> {
1135 pub fn num(&self) -> Option<u8> {
1136 self.0[CTA_PROTO_NUM as usize].map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1137 }
1138
1139 pub fn src_port(&self) -> Option<u16> {
1140 self.0[CTA_PROTO_SRC_PORT as usize]
1141 .map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
1142 }
1143
1144 pub fn dst_port(&self) -> Option<u16> {
1145 self.0[CTA_PROTO_DST_PORT as usize]
1146 .map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
1147 }
1148
1149 pub fn icmp_id(&self) -> Option<u16> {
1150 self.0[CTA_PROTO_ICMP_ID as usize].map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
1151 }
1152
1153 pub fn icmp_type(&self) -> Option<u8> {
1154 self.0[CTA_PROTO_ICMP_TYPE as usize].map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1155 }
1156
1157 pub fn icmp_code(&self) -> Option<u8> {
1158 self.0[CTA_PROTO_ICMP_CODE as usize].map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1159 }
1160
1161 pub fn icmp6_id(&self) -> Option<u16> {
1162 self.0[CTA_PROTO_ICMPV6_ID as usize]
1163 .map(|nla| u16::from_be(unsafe { mnl_attr_get_u16(nla) }))
1164 }
1165
1166 pub fn icmp6_type(&self) -> Option<u8> {
1167 self.0[CTA_PROTO_ICMPV6_TYPE as usize]
1168 .map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1169 }
1170
1171 pub fn icmp6_code(&self) -> Option<u8> {
1172 self.0[CTA_PROTO_ICMPV6_CODE as usize]
1173 .map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1174 }
1175}
1176
1177impl Debug for CtTupleProto<'_> {
1178 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1179 f.debug_struct("CtTupleProto")
1180 .field("num", &self.num())
1181 .field("src_port", &self.src_port())
1182 .field("dst_port", &self.dst_port())
1183 .field("icmp_id", &self.icmp_id())
1184 .field("icmp_type", &self.icmp_type())
1185 .field("icmp_code", &self.icmp_code())
1186 .field("icmp6_id", &self.icmp6_id())
1187 .field("icmp6_type", &self.icmp6_type())
1188 .field("icmp6_code", &self.icmp6_code())
1189 .finish()
1190 }
1191}
1192
1193#[derive(Copy, Clone)]
1194pub struct CtProtoInfo<'a>([Option<&'a nlattr>; CTA_PROTOINFO_MAX as usize + 1]);
1195
1196impl<'a> CtProtoInfo<'a> {
1197 pub fn tcp(&self) -> Option<CtProtoInfoTcp<'a>> {
1198 self.0[CTA_PROTOINFO_TCP as usize].map(|nla| {
1199 let mut tb = [None; CTA_PROTOINFO_TCP_MAX as usize + 1];
1200 if unsafe {
1201 mnl_attr_parse_nested(
1202 nla,
1203 Some(parse_protoinfo_tcp_attr_cb),
1204 tb.as_mut_ptr() as *mut c_void,
1205 )
1206 } < 0
1207 {
1208 return None;
1209 }
1210 Some(CtProtoInfoTcp(tb))
1211 })?
1212 }
1213}
1214
1215impl Debug for CtProtoInfo<'_> {
1216 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1217 f.debug_struct("CtProtoInfo")
1218 .field("tcp", &self.tcp())
1219 .finish()
1220 }
1221}
1222
1223extern "C" fn parse_protoinfo_tcp_attr_cb(attr: *const nlattr, data: *mut c_void) -> c_int {
1224 let tb = data as *mut Option<&nlattr>;
1225 let attr_type = unsafe { mnl_attr_get_type(attr) } as u32;
1226 if unsafe { mnl_attr_type_valid(attr, CTA_PROTOINFO_TCP_MAX as u16) } < 0 {
1227 return MNL_CB_OK as c_int;
1228 }
1229 match attr_type {
1230 CTA_PROTOINFO_TCP_STATE
1231 | CTA_PROTOINFO_TCP_WSCALE_ORIGINAL
1232 | CTA_PROTOINFO_TCP_WSCALE_REPLY => {
1233 if unsafe { mnl_attr_validate(attr, MNL_TYPE_U8) } < 0 {
1234 return MNL_CB_ERROR;
1235 }
1236 }
1237 CTA_PROTOINFO_TCP_FLAGS_ORIGINAL | CTA_PROTOINFO_TCP_FLAGS_REPLY => {
1238 if unsafe { mnl_attr_validate2(attr, MNL_TYPE_UNSPEC, 2) } < 0 {
1239 return MNL_CB_ERROR;
1240 }
1241 }
1242 _ => {}
1243 }
1244 unsafe { *tb.add(attr_type as usize) = Some(&*attr) };
1245 MNL_CB_OK as c_int
1246}
1247
1248#[derive(Copy, Clone)]
1249pub struct CtProtoInfoTcp<'a>([Option<&'a nlattr>; CTA_PROTOINFO_TCP_MAX as usize + 1]);
1250
1251impl<'a> CtProtoInfoTcp<'a> {
1252 pub fn state(&self) -> Option<u8> {
1253 self.0[CTA_PROTOINFO_TCP_STATE as usize]
1254 .map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1255 }
1256
1257 pub fn wscale_original(&self) -> Option<u8> {
1258 self.0[CTA_PROTOINFO_TCP_WSCALE_ORIGINAL as usize]
1259 .map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1260 }
1261
1262 pub fn wscale_reply(&self) -> Option<u8> {
1263 self.0[CTA_PROTOINFO_TCP_WSCALE_REPLY as usize]
1264 .map(|nla| u8::from_be(unsafe { mnl_attr_get_u8(nla) }))
1265 }
1266}
1267
1268impl Debug for CtProtoInfoTcp<'_> {
1269 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1270 f.debug_struct("CtProtoInfoTcp")
1271 .field("state", &self.state())
1272 .field("wscale_original", &self.wscale_original())
1273 .field("wscale_reply", &self.wscale_reply())
1274 .finish()
1275 }
1276}
1277
1278#[derive(Copy, Clone)]
1279pub struct CtCounters<'a>([Option<&'a nlattr>; CTA_COUNTERS_MAX as usize + 1]);
1280
1281impl<'a> CtCounters<'a> {
1282 pub fn packets(&self) -> Option<u64> {
1283 self.0[CTA_COUNTERS_PACKETS as usize]
1284 .map(|nla| u64::from_be(unsafe { mnl_attr_get_u64(nla) }))
1285 }
1286
1287 pub fn bytes(&self) -> Option<u64> {
1288 self.0[CTA_COUNTERS_BYTES as usize]
1289 .map(|nla| u64::from_be(unsafe { mnl_attr_get_u64(nla) }))
1290 }
1291}
1292
1293impl Debug for CtCounters<'_> {
1294 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1295 f.debug_struct("CtCounters")
1296 .field("packets", &self.packets())
1297 .field("bytes", &self.bytes())
1298 .finish()
1299 }
1300}
1301
1302#[derive(Copy, Clone)]
1303pub struct CtTimestamp<'a>([Option<&'a nlattr>; CTA_TIMESTAMP_MAX as usize + 1]);
1304
1305impl<'a> CtTimestamp<'a> {
1306 pub fn start(&self) -> Option<u64> {
1307 self.0[CTA_TIMESTAMP_START as usize]
1308 .map(|nla| u64::from_be(unsafe { mnl_attr_get_u64(nla) }))
1309 }
1310
1311 pub fn stop(&self) -> Option<u64> {
1312 self.0[CTA_TIMESTAMP_STOP as usize]
1313 .map(|nla| u64::from_be(unsafe { mnl_attr_get_u64(nla) }))
1314 }
1315}
1316
1317impl Debug for CtTimestamp<'_> {
1318 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1319 f.debug_struct("CtTimestamp")
1320 .field("start", &self.start())
1321 .field("stop", &self.stop())
1322 .finish()
1323 }
1324}