From e5d5e10de51333b0a27631162e79338b513545a7 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Thu, 12 Nov 2026 16:47:04 +0700 Subject: [PATCH 1/2] nf_tables: payload that writes the packet and queue, as nft_payload.c and nft_queue.c of Linux --- CHANGELOG.md | 1 + net/rk-net/src/netfilter/nf_nat.rs | 2 +- net/rk-net/src/netfilter/nf_tables_core.rs | 152 ++++++++++++- net/rk-net/src/netfilter/nft_exprs.rs | 248 ++++++++++++++++++++- 4 files changed, 390 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b849b11..1fc05d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This file records each release. The format follows [Keep a Changelog](https://ke - fanotify has `FAN_REPORT_MNT`. A group with this flag marks a mount namespace with `FAN_MARK_MNTNS` and gets `FAN_MNT_ATTACH` and `FAN_MNT_DETACH` when a mount of the namespace is attached, moved or unmounted. Each event has a `FAN_EVENT_INFO_TYPE_MNT` record with the unique mount ID, and the errors and the record layout are the same as in Linux. The `init` boot test checks the errors, the fdinfo line and the events of mount, move and umount. (#539) - The `sdbpf` boot test runs the unit tests test-bpf-devices, test-bpf-firewall and test-bpf-foreign-programs of systemd. They attach cgroup programs with `BPF_PROG_ATTACH`, `BPF_PROG_DETACH`, `BPF_PROG_QUERY` and `BPF_LINK_CREATE`, and all three pass. `RK_SYSTEMD_PACKAGES` of tools/systemd/build.sh adds packages to the systemd tree, and this test needs `systemd-tests` and `iputils-ping` (#537). +- The expression `payload` of nf_tables writes the packet, as `nft_payload_set_eval` of Linux, with the checksums of IP, of the transport header and of SCTP. The expression `queue`, as nft_queue.c of Linux, gives the packet to its queue (#542). ### Fixed diff --git a/net/rk-net/src/netfilter/nf_nat.rs b/net/rk-net/src/netfilter/nf_nat.rs index f246f52..d50dd8d 100644 --- a/net/rk-net/src/netfilter/nf_nat.rs +++ b/net/rk-net/src/netfilter/nf_nat.rs @@ -367,7 +367,7 @@ fn csum_addr(d: &mut [u8], at: usize, old: &[u8], new: &[u8]) { } /// The CRC32c of SCTP, as `sctp_compute_cksum` of Linux, with the checksum field as 0. -fn sctp_crc(d: &[u8]) -> u32 { +pub(super) fn sctp_crc(d: &[u8]) -> u32 { let mut crc = !0u32; for (i, &b) in d.iter().enumerate() { let b = if (8..12).contains(&i) { 0 } else { b }; diff --git a/net/rk-net/src/netfilter/nf_tables_core.rs b/net/rk-net/src/netfilter/nf_tables_core.rs index 73349b0..cb2269d 100644 --- a/net/rk-net/src/netfilter/nf_tables_core.rs +++ b/net/rk-net/src/netfilter/nf_tables_core.rs @@ -58,10 +58,11 @@ mod nft_objects; #[cfg(test)] pub(crate) use self::nft_exprs::nft_log_line; use self::nft_exprs::{ - NftByteorder, NftHash, NftLimit, NftNumgen, NftRange, NftReject, nft_byteorder_eval, - nft_byteorder_init, nft_hash_eval, nft_hash_init, nft_limit_eval, nft_limit_init, nft_log_init, - nft_numgen_eval, nft_numgen_init, nft_prandom, nft_range_eval, nft_range_init, nft_realtime_ns, - nft_reject_eval, nft_reject_init, + NftByteorder, NftHash, NftLimit, NftNumgen, NftPayloadSet, NftQueue, NftRange, NftReject, + nft_byteorder_eval, nft_byteorder_init, nft_hash_eval, nft_hash_init, nft_limit_eval, + nft_limit_init, nft_log_init, nft_numgen_eval, nft_numgen_init, nft_payload_set_eval, + nft_payload_set_init, nft_prandom, nft_queue_eval, nft_queue_init, nft_range_eval, + nft_range_init, nft_realtime_ns, nft_reject_eval, nft_reject_init, }; pub(crate) use self::nft_exprs::{NftLog, nft_log_eval}; use self::nft_flowtable::nft_flow_offload_init; @@ -298,6 +299,10 @@ pub(super) enum NftExpr { Numgen(NftNumgen), /// `hash`, as nft_hash.c. Hash(NftHash), + /// `payload` that writes a register to the packet, as `nft_payload_set_eval`. + PayloadSet(NftPayloadSet), + /// `queue`, as nft_queue.c. + Queue(NftQueue), /// `limit`, as nft_limit.c. The copies of the rule share the bucket. Limit(Arc), /// `quota`, as nft_quota.c. The copies of the rule share the bytes. @@ -558,11 +563,7 @@ pub(super) fn nf_tables_expr_parse( Ok(NftExpr::Cmp { sreg, op, data: v }) } b"payload" => { - let a = Attrs::parse(data, 5); - if a.has(NFTA_PAYLOAD_SREG) { - // The change of the packet comes later. - return Err(Errno::EOPNOTSUPP); - } + let a = Attrs::parse(data, 8); let base = match be32(&a, NFTA_PAYLOAD_BASE)? { Some( b @ (NFT_PAYLOAD_LL_HEADER @@ -572,6 +573,12 @@ pub(super) fn nf_tables_expr_parse( Some(_) => return Err(Errno::EOPNOTSUPP), None => return Err(Errno::EINVAL), }; + if a.has(NFTA_PAYLOAD_SREG) { + if a.has(NFTA_PAYLOAD_DREG) { + return Err(Errno::EINVAL); + } + return Ok(NftExpr::PayloadSet(nft_payload_set_init(&a, base)?)); + } let Some(offset) = be32(&a, NFTA_PAYLOAD_OFFSET)? else { return Err(Errno::EINVAL) }; if offset > u32::from(u8::MAX) { return Err(Errno::ERANGE); @@ -707,6 +714,7 @@ pub(super) fn nf_tables_expr_parse( b"byteorder" => Ok(NftExpr::Byteorder(nft_byteorder_init(data)?)), b"range" => Ok(NftExpr::Range(nft_range_init(data)?)), b"numgen" => Ok(NftExpr::Numgen(nft_numgen_init(data)?)), + b"queue" => Ok(NftExpr::Queue(nft_queue_init(data)?)), b"hash" => Ok(NftExpr::Hash(nft_hash_init(data)?)), b"limit" => Ok(NftExpr::Limit(nft_limit_init(data)?)), b"quota" => Ok(NftExpr::Quota(nft_quota_init(data)?)), @@ -1748,6 +1756,8 @@ fn nft_eval( NftExpr::Byteorder(b) => nft_byteorder_eval(b, regs), NftExpr::Range(r) => nft_range_eval(r, regs), NftExpr::Numgen(n) => nft_numgen_eval(n, regs), + NftExpr::PayloadSet(p) => nft_payload_set_eval(p, regs, skb, pkt), + NftExpr::Queue(q) => nft_queue_eval(q, regs), NftExpr::Hash(h) => nft_hash_eval(h, regs, skb, state.pf, pkt), NftExpr::Limit(l) => nft_limit_eval(l, regs, skb), NftExpr::Quota(q) => nft_quota_eval(q, regs, skb), @@ -1824,7 +1834,9 @@ pub(super) fn nft_do_chain( #[cfg(test)] mod tests { + use super::nft_exprs::{NFT_PAYLOAD_CSUM_INET, NFT_PAYLOAD_CSUM_NONE}; use super::*; + use crate::netfilter::nf_nat::{csum_add, csum_fold}; use crate::netfilter::{NF_ACCEPT, NF_INET_LOCAL_OUT}; use alloc::vec; @@ -2301,4 +2313,126 @@ mod tests { assert_eq!(reg(NFT_REG_VERDICT, 4), Err(Errno::EINVAL)); assert_eq!(reg(1, 0), Err(Errno::EINVAL)); } + + /// An IPv4 TCP packet from 10.0.0.1:1000 to 10.0.0.2:80 with good checksums. + fn tcp4() -> NetBuf { + let mut p = vec![0u8; 44]; + p[0] = 0x45; + p[2..4].copy_from_slice(&44u16.to_be_bytes()); + p[8] = 64; + p[9] = 6; + p[12..16].copy_from_slice(&[10, 0, 0, 1]); + p[16..20].copy_from_slice(&[10, 0, 0, 2]); + p[20..22].copy_from_slice(&1000u16.to_be_bytes()); + p[22..24].copy_from_slice(&80u16.to_be_bytes()); + p[32] = 0x50; + p[40..44].copy_from_slice(b"data"); + fix_csums(&mut p); + NetBuf::from_slice(&p) + } + + /// Computes the checksums of the IPv4 header and the TCP header of `p` again. + fn fix_csums(p: &mut [u8]) { + p[10..12].fill(0); + let s = csum_fold(csum_add(0, &p[..20])); + p[10..12].copy_from_slice(&s.to_be_bytes()); + p[36..38].fill(0); + let mut s = csum_add(0, &p[12..20]); + s = csum_add(s, &[0, 6, 0, 24]); + let s = csum_fold(csum_add(s, &p[20..])); + p[36..38].copy_from_slice(&s.to_be_bytes()); + } + + fn payload_set( + base: u32, + offset: usize, + len: usize, + csum_type: u32, + csum_offset: usize, + l4csum: bool, + ) -> NftExpr { + NftExpr::PayloadSet(NftPayloadSet { + base, + offset, + len, + sreg: R1, + csum_type, + csum_offset, + l4csum, + }) + } + + #[test] + fn payload_set_writes_the_packet_and_changes_the_checksums() { + let s = state(NFPROTO_IPV4); + // ip daddr set 192.168.7.9, with the checksums of the IP header and of TCP. + let r = vec![ + e(NftExpr::Immediate { dreg: R1, data: NftImm::Value(vec![192, 168, 7, 9]) }), + e(payload_set(NFT_PAYLOAD_NETWORK_HEADER, 16, 4, NFT_PAYLOAD_CSUM_INET, 10, true)), + ]; + let prog = NftProg { chains: vec![vec![r]], sets: vec![], nat: false }; + let mut p = tcp4(); + assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut p, &s), NF_ACCEPT); + let mut want = tcp4().data().to_vec(); + want[16..20].copy_from_slice(&[192, 168, 7, 9]); + fix_csums(&mut want); + assert_eq!(p.data(), &want[..]); + + // tcp dport set 8080, with the checksum of TCP at offset 16. + let r = vec![ + e(NftExpr::Immediate { dreg: R1, data: NftImm::Value(8080u16.to_be_bytes().to_vec()) }), + e(payload_set(NFT_PAYLOAD_TRANSPORT_HEADER, 2, 2, NFT_PAYLOAD_CSUM_INET, 16, false)), + ]; + let prog = NftProg { chains: vec![vec![r]], sets: vec![], nat: false }; + let mut p = tcp4(); + assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut p, &s), NF_ACCEPT); + let mut want = tcp4().data().to_vec(); + want[22..24].copy_from_slice(&8080u16.to_be_bytes()); + fix_csums(&mut want); + assert_eq!(p.data(), &want[..]); + + // A write past the end of the packet ends the rule, and the packet stays. + let r = vec![ + e(NftExpr::Immediate { dreg: R1, data: NftImm::Value(vec![1, 2, 3, 4]) }), + e(payload_set(NFT_PAYLOAD_TRANSPORT_HEADER, 22, 4, NFT_PAYLOAD_CSUM_NONE, 0, false)), + verdict(NF_DROP, 0), + ]; + let prog = NftProg { chains: vec![vec![r]], sets: vec![], nat: false }; + let mut p = tcp4(); + assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut p, &s), NF_ACCEPT); + assert_eq!(p.data(), tcp4().data()); + + // A UDP datagram with no checksum gets the write and keeps no checksum, as in Linux. + let r = vec![ + e(NftExpr::Immediate { dreg: R1, data: NftImm::Value(vec![10, 0, 0, 3]) }), + e(payload_set(NFT_PAYLOAD_NETWORK_HEADER, 16, 4, NFT_PAYLOAD_CSUM_NONE, 0, true)), + verdict(NF_DROP, 0), + ]; + let prog = NftProg { chains: vec![vec![r]], sets: vec![], nat: false }; + let mut p = udp4(9); + assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut p, &s), NF_DROP); + let mut want = udp4(9).data().to_vec(); + want[16..20].copy_from_slice(&[10, 0, 0, 3]); + assert_eq!(p.data(), &want[..]); + assert_eq!(p.data()[26..28], [0, 0]); + } + + #[test] + fn queue_gives_the_verdict_of_the_queue() { + use crate::netfilter::{NF_VERDICT_FLAG_QUEUE_BYPASS, nf_queue_nr}; + let s = state(NFPROTO_IPV4); + let q = |data: &[u8]| { + let r = vec![e(NftExpr::Queue(nft_queue_init(data).unwrap()))]; + let prog = NftProg { chains: vec![vec![r]], sets: vec![], nat: false }; + nft_do_chain(&prog, NF_ACCEPT, &mut udp4(9), &s) + }; + // queue num 3 bypass: NFTA_QUEUE_NUM and NFTA_QUEUE_FLAGS. + let a = [6, 0, 1, 0, 0, 3, 0, 0, 6, 0, 3, 0, 0, 1, 0, 0]; + assert_eq!(q(&a), nf_queue_nr(3) | NF_VERDICT_FLAG_QUEUE_BYPASS); + assert_eq!(q(&a[..8]), nf_queue_nr(3)); + // A queue number and a register together are not valid, and neither is no queue. + let both = [6, 0, 1, 0, 0, 3, 0, 0, 8, 0, 4, 0, 0, 0, 0, 1]; + assert_eq!(nft_queue_init(&both).unwrap_err(), Errno::EINVAL); + assert_eq!(nft_queue_init(&a[8..]).unwrap_err(), Errno::EINVAL); + } } diff --git a/net/rk-net/src/netfilter/nft_exprs.rs b/net/rk-net/src/netfilter/nft_exprs.rs index a8d0fad..fba69f9 100644 --- a/net/rk-net/src/netfilter/nft_exprs.rs +++ b/net/rk-net/src/netfilter/nft_exprs.rs @@ -1,12 +1,21 @@ //! The expressions `byteorder`, `range`, `limit`, `log`, `reject`, `numgen` and `hash` of //! nf_tables, as nft_byteorder.c, nft_range.c, nft_limit.c, nft_log.c, nf_log_syslog.c, //! nft_reject.c, nft_reject_ipv4.c, nft_reject_ipv6.c, nft_reject_inet.c, nft_numgen.c and -//! nft_hash.c of Linux. +//! nft_hash.c of Linux, and `payload` that writes the packet and `queue`, as nft_payload.c and +//! nft_queue.c of Linux. -use super::{NFT_BREAK, NftRegs, be32, be64, len_attr, nft_parse_register, nft_value}; +use super::{ + NFT_BREAK, NFT_PAYLOAD_NETWORK_HEADER, NFT_PAYLOAD_TRANSPORT_HEADER, NFTA_PAYLOAD_LEN, + NFTA_PAYLOAD_OFFSET, NFTA_PAYLOAD_SREG, NftRegs, be32, be64, len_attr, nft_parse_register, + nft_value, +}; use crate::buf::NetBuf; +use crate::netfilter::nf_nat::{csum_add, csum_fold, sctp_crc}; use crate::netfilter::nf_reject::{NfReject, nf_send_reject}; -use crate::netfilter::{NFPROTO_INET, NFPROTO_IPV4, NFPROTO_IPV6, NfHookState, NfPktinfo}; +use crate::netfilter::{ + NF_VERDICT_FLAG_QUEUE_BYPASS, NFPROTO_INET, NFPROTO_IPV4, NFPROTO_IPV6, NfHookState, NfPktinfo, + nf_queue_nr, +}; use crate::netlink::msg::Attrs; use alloc::sync::Arc; use alloc::vec::Vec; @@ -1173,3 +1182,236 @@ mod tests { assert!(!nat && mask & 1 << crate::netfilter::NF_INET_POST_ROUTING == 0); } } + +// `NFTA_PAYLOAD_CSUM_*`, `enum nft_payload_csum_types` and `enum nft_payload_csum_flags`. +const NFTA_PAYLOAD_CSUM_TYPE: u32 = 6; +const NFTA_PAYLOAD_CSUM_OFFSET: u32 = 7; +const NFTA_PAYLOAD_CSUM_FLAGS: u32 = 8; +pub(super) const NFT_PAYLOAD_CSUM_NONE: u32 = 0; +pub(super) const NFT_PAYLOAD_CSUM_INET: u32 = 1; +pub(super) const NFT_PAYLOAD_CSUM_SCTP: u32 = 2; +const NFT_PAYLOAD_L4CSUM_PSEUDOHDR: u32 = 1; + +const IPPROTO_TCP: u8 = 6; +const IPPROTO_UDP: u8 = 17; +const IPPROTO_ICMPV6: u8 = 58; +const IPPROTO_SCTP: u8 = 132; +const IPPROTO_UDPLITE: u8 = 136; + +/// `payload` that writes a register to the packet, as `nft_payload_set` of Linux. +#[derive(Clone, Debug)] +pub(crate) struct NftPayloadSet { + pub(super) base: u32, + pub(super) offset: usize, + pub(super) len: usize, + pub(super) sreg: usize, + pub(super) csum_type: u32, + pub(super) csum_offset: usize, + /// `NFT_PAYLOAD_L4CSUM_PSEUDOHDR`: the bytes are in the pseudo header of the checksum of + /// the transport header too. + pub(super) l4csum: bool, +} + +/// Reads `payload` with `NFTA_PAYLOAD_SREG`, as `nft_payload_set_init` of Linux. `base` is a +/// base that the packet can have. +pub(super) fn nft_payload_set_init(a: &Attrs<'_>, base: u32) -> Result { + let Some(offset) = be32(a, NFTA_PAYLOAD_OFFSET)? else { return Err(Errno::EINVAL) }; + if offset > u32::from(u8::MAX) { + return Err(Errno::ERANGE); + } + let len = len_attr(a, NFTA_PAYLOAD_LEN)?; + let csum_type = be32(a, NFTA_PAYLOAD_CSUM_TYPE)?.unwrap_or(NFT_PAYLOAD_CSUM_NONE); + let csum_offset = match be32(a, NFTA_PAYLOAD_CSUM_OFFSET)? { + Some(o) if o > u32::from(u8::MAX) => return Err(Errno::ERANGE), + o => o.unwrap_or(0) as usize, + }; + let flags = be32(a, NFTA_PAYLOAD_CSUM_FLAGS)?.unwrap_or(0); + if flags & !NFT_PAYLOAD_L4CSUM_PSEUDOHDR != 0 { + return Err(Errno::EINVAL); + } + match csum_type { + NFT_PAYLOAD_CSUM_NONE | NFT_PAYLOAD_CSUM_INET => {} + // The checksum of SCTP is at offset 8 of its header. + NFT_PAYLOAD_CSUM_SCTP if base == NFT_PAYLOAD_TRANSPORT_HEADER && csum_offset == 8 => {} + NFT_PAYLOAD_CSUM_SCTP => return Err(Errno::EINVAL), + _ => return Err(Errno::EOPNOTSUPP), + } + let sreg = nft_parse_register(a, NFTA_PAYLOAD_SREG, len)?; + Ok(NftPayloadSet { + base, + offset: offset as usize, + len, + sreg, + csum_type, + csum_offset, + l4csum: flags != 0, + }) +} + +/// Writes the register of `p` to the packet and changes the checksums, as +/// `nft_payload_set_eval` of Linux. A write that the packet cannot take ends the rule. +pub(super) fn nft_payload_set_eval( + p: &NftPayloadSet, + regs: &mut NftRegs, + skb: &mut NetBuf, + pkt: &NfPktinfo, +) { + let src = ®s.data[p.sreg..p.sreg + p.len]; + if nft_payload_set(skb, pkt, src, p).is_none() { + regs.verdict = NFT_BREAK; + } +} + +fn nft_payload_set(skb: &mut NetBuf, pkt: &NfPktinfo, src: &[u8], p: &NftPayloadSet) -> Option<()> { + let start = match p.base { + NFT_PAYLOAD_NETWORK_HEADER => 0, + NFT_PAYLOAD_TRANSPORT_HEADER => pkt.thoff?, + // The packet has no link layer header at the hooks of IPv4 and IPv6. + _ => return None, + }; + let at = start + p.offset; + let end = at + src.len(); + let d = skb.data_mut(); + if end > d.len() { + return None; + } + if p.csum_type == NFT_PAYLOAD_CSUM_INET || p.l4csum { + let fsum = csum_add(0, &d[at..end]); + let tsum = csum_add(0, src); + if p.csum_type == NFT_PAYLOAD_CSUM_INET { + nft_csum_replace(d, start + p.csum_offset, fsum, tsum)?; + } + // A packet with no checksum of the transport header keeps none, as + // `nft_payload_l4csum_update` of Linux. + if p.l4csum + && let Some(l4) = nft_payload_l4csum_offset(pkt, d) + { + nft_csum_replace(d, l4, fsum, tsum)?; + } + } + d[at..end].copy_from_slice(src); + if p.csum_type == NFT_PAYLOAD_CSUM_SCTP && pkt.l4proto == IPPROTO_SCTP { + let t = pkt.thoff?; + let ip_end = nft_ip_end(d).min(d.len()); + if ip_end < t + 12 { + return None; + } + let crc = sctp_crc(&d[t..ip_end]); + d[t + 8..t + 12].copy_from_slice(&crc.to_le_bytes()); + } + Some(()) +} + +/// Changes the checksum at `at` of `d` for the bytes of the sum `tsum` in place of the bytes +/// of the sum `fsum`, as `nft_csum_replace` of Linux. +fn nft_csum_replace(d: &mut [u8], at: usize, fsum: u32, tsum: u32) -> Option<()> { + let w = d.get_mut(at..at + 2)?; + let old = u16::from_be_bytes([w[0], w[1]]); + // `csum_fold` gives the complement of the sum. + let s = u32::from(!old) + u32::from(csum_fold(fsum)) + u32::from(!csum_fold(tsum)); + let sum = match csum_fold(s) { + 0 => 0xffff, + s => s, + }; + w.copy_from_slice(&sum.to_be_bytes()); + Some(()) +} + +/// The offset of the checksum of the transport header that has the pseudo header, as +/// `nft_payload_l4csum_offset` of Linux. A UDP datagram with no checksum has none. +fn nft_payload_l4csum_offset(pkt: &NfPktinfo, d: &[u8]) -> Option { + let t = pkt.thoff?; + let at = match pkt.l4proto { + IPPROTO_TCP => 16, + IPPROTO_UDP => { + if d.get(t + 6..t + 8)? == [0, 0] { + return None; + } + 6 + } + IPPROTO_UDPLITE => 6, + IPPROTO_ICMPV6 => 2, + _ => return None, + }; + Some(t + at) +} + +/// The end of the IP packet `d`, from the length in its header. +fn nft_ip_end(d: &[u8]) -> usize { + match d.first().map(|b| b >> 4) { + Some(4) if d.len() >= 4 => usize::from(u16::from_be_bytes([d[2], d[3]])), + Some(6) if d.len() >= 6 => 40 + usize::from(u16::from_be_bytes([d[4], d[5]])), + _ => d.len(), + } +} + +// `NFTA_QUEUE_*` and `NFT_QUEUE_FLAG_*`. +const NFTA_QUEUE_NUM: u32 = 1; +const NFTA_QUEUE_TOTAL: u32 = 2; +const NFTA_QUEUE_FLAGS: u32 = 3; +const NFTA_QUEUE_SREG_QNUM: u32 = 4; +const NFT_QUEUE_FLAG_BYPASS: u16 = 0x01; +const NFT_QUEUE_FLAG_CPU_FANOUT: u16 = 0x02; +const NFT_QUEUE_FLAG_MASK: u16 = 0x03; + +fn be16_attr(a: &Attrs<'_>, ty: u32) -> Result> { + match a.get(ty) { + None => Ok(None), + Some(&[x, y]) => Ok(Some(u16::from_be_bytes([x, y]))), + Some(_) => Err(Errno::EINVAL), + } +} + +/// `queue`, as nft_queue.c of Linux: the queue `num`, or the queue in the register `sreg`. +#[derive(Clone, Debug)] +pub(crate) struct NftQueue { + num: u16, + sreg: Option, + bypass: bool, +} + +/// Reads `queue`, as `nft_queue_select_ops`, `nft_queue_init` and `nft_queue_sreg_init` of +/// Linux. +pub(super) fn nft_queue_init(data: &[u8]) -> Result { + let a = Attrs::parse(data, 4); + let flags = be16_attr(&a, NFTA_QUEUE_FLAGS)?.unwrap_or(0); + if flags & !NFT_QUEUE_FLAG_MASK != 0 { + return Err(Errno::EINVAL); + } + let bypass = flags & NFT_QUEUE_FLAG_BYPASS != 0; + match (a.has(NFTA_QUEUE_NUM), a.has(NFTA_QUEUE_SREG_QNUM)) { + (true, true) | (false, false) => Err(Errno::EINVAL), + (true, false) => { + let num = be16_attr(&a, NFTA_QUEUE_NUM)?.unwrap_or(0); + let total = be16_attr(&a, NFTA_QUEUE_TOTAL)?.unwrap_or(1); + if total == 0 { + return Err(Errno::EINVAL); + } + if u32::from(total) - 1 + u32::from(num) > u32::from(u16::MAX) { + return Err(Errno::ERANGE); + } + Ok(NftQueue { num, sreg: None, bypass }) + } + (false, true) => { + let sreg = nft_parse_register(&a, NFTA_QUEUE_SREG_QNUM, 4)?; + if flags & NFT_QUEUE_FLAG_CPU_FANOUT != 0 { + return Err(Errno::EOPNOTSUPP); + } + Ok(NftQueue { num: 0, sreg: Some(sreg), bypass }) + } + } +} + +/// Gives the packet to its queue, as `nft_queue_eval` and `nft_queue_sreg_eval` of Linux. A +/// spread of the packets over more than one queue is not here: the first queue gets them. +pub(super) fn nft_queue_eval(q: &NftQueue, regs: &mut NftRegs) { + let num = match q.sreg { + Some(r) => { + let w = ®s.data[r..r + 4]; + u32::from_ne_bytes([w[0], w[1], w[2], w[3]]) as u16 + } + None => q.num, + }; + let bypass = if q.bypass { NF_VERDICT_FLAG_QUEUE_BYPASS } else { 0 }; + regs.verdict = nf_queue_nr(num) | bypass; +} From 3d8e4d35e80ac6a2bff4488fda9dd53378a29d1a Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:43:16 +0700 Subject: [PATCH 2/2] Merge the Unreleased sections of the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc05d8..bd2f5e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This file records each release. The format follows [Keep a Changelog](https://ke - The `getdents` call of x86_64, with the records of the old format `linux_dirent`, as `filldir` of Linux. The `readdir` test of the AppArmor regression tests uses it (#540). - AppArmor checks the path operations, as the path hooks of lsm.c of Linux: a create, a `mkdir`, a `mknod`, a symbolic link, a hard link, a rename, an `unlink`, an `rmdir`, a truncate, a `chmod`, a `chown` and a `stat`. The VFS has the hooks `path_*`, `inode_getattr` and `file_truncate` of Linux, at the places where Linux calls them (#540). - The expressions `numgen` and `hash` of nf_tables, as nft_numgen.c and nft_hash.c of Linux, and more keys of `meta`, as nft_meta.c: `priority`, `pkttype`, `cpu`, `iifgroup`, `oifgroup`, `random`, `iifkind`, `oifkind`, `time`, `day`, `hour`, `sdif` and `sdifname`. A rule sets `meta priority` and `meta pkttype` too. A packet now has a priority and a type, as `skb->priority` and `skb->pkt_type`. `meta iif` and `meta iifname` of a packet with no device give 0 and "", as in Linux (#542). +- The expression `payload` of nf_tables writes the packet, as `nft_payload_set_eval` of Linux, with the checksums of IP, of the transport header and of SCTP. The expression `queue`, as nft_queue.c of Linux, gives the packet to its queue (#542). ## [0.5.24] - 2026-11-12 @@ -17,7 +18,6 @@ This file records each release. The format follows [Keep a Changelog](https://ke - fanotify has `FAN_REPORT_MNT`. A group with this flag marks a mount namespace with `FAN_MARK_MNTNS` and gets `FAN_MNT_ATTACH` and `FAN_MNT_DETACH` when a mount of the namespace is attached, moved or unmounted. Each event has a `FAN_EVENT_INFO_TYPE_MNT` record with the unique mount ID, and the errors and the record layout are the same as in Linux. The `init` boot test checks the errors, the fdinfo line and the events of mount, move and umount. (#539) - The `sdbpf` boot test runs the unit tests test-bpf-devices, test-bpf-firewall and test-bpf-foreign-programs of systemd. They attach cgroup programs with `BPF_PROG_ATTACH`, `BPF_PROG_DETACH`, `BPF_PROG_QUERY` and `BPF_LINK_CREATE`, and all three pass. `RK_SYSTEMD_PACKAGES` of tools/systemd/build.sh adds packages to the systemd tree, and this test needs `systemd-tests` and `iputils-ping` (#537). -- The expression `payload` of nf_tables writes the packet, as `nft_payload_set_eval` of Linux, with the checksums of IP, of the transport header and of SCTP. The expression `queue`, as nft_queue.c of Linux, gives the packet to its queue (#542). ### Fixed