From 8d448d9e201a572b1b060d20ee4013265e34bd89 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Wed, 11 Nov 2026 23:14:09 +0700 Subject: [PATCH 1/2] nf_tables: numgen, hash and more meta keys, as nft_numgen.c, nft_hash.c and nft_meta.c of Linux --- CHANGELOG.md | 1 + net/rk-net-inet/src/stack.rs | 8 + net/rk-net/src/buf.rs | 12 + net/rk-net/src/netfilter/nf_tables_core.rs | 203 +++++++++-- net/rk-net/src/netfilter/nft_exprs.rs | 389 ++++++++++++++++++++- 5 files changed, 576 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4bc6ac..e0617d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,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 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). ### Fixed diff --git a/net/rk-net-inet/src/stack.rs b/net/rk-net-inet/src/stack.rs index f10c61e..6023e4f 100644 --- a/net/rk-net-inet/src/stack.rs +++ b/net/rk-net-inet/src/stack.rs @@ -865,6 +865,14 @@ pub fn receive(dev: &NetDevice, mut frame: NetBuf) { return; } frame.protocol = eth.ethertype; + // The type of the frame, as `eth_type_trans` of Linux. + frame.pkt_type = if eth.dst.is_broadcast() { + rk_net::packet::PACKET_BROADCAST + } else if eth.dst.is_multicast() { + rk_net::packet::PACKET_MULTICAST + } else { + rk_net::packet::PACKET_HOST + }; match u32::from(eth.ethertype) { ETH_P_ARP => arp_input(dev, frame.data()), // A frame to a link multicast or broadcast address is not `PACKET_HOST`, and the stack diff --git a/net/rk-net/src/buf.rs b/net/rk-net/src/buf.rs index 3a23982..234e78f 100644 --- a/net/rk-net/src/buf.rs +++ b/net/rk-net/src/buf.rs @@ -40,6 +40,12 @@ pub struct NetBuf { /// The mark of the packet, as `skb->mark` of Linux. A rule of nf_tables sets it with /// `meta mark set` and reads it with `meta mark`. pub mark: u32, + /// The priority of the packet, as `skb->priority` of Linux. A rule of nf_tables sets it + /// with `meta priority set` and reads it with `meta priority`. + pub priority: u32, + /// The type of the link address that the frame came to, `PACKET_HOST`, + /// `PACKET_BROADCAST` or `PACKET_MULTICAST`, as `skb->pkt_type` of Linux. + pub pkt_type: u8, } impl NetBuf { @@ -58,6 +64,8 @@ impl NetBuf { csum_ok: false, nfct: NfCt::default(), mark: 0, + priority: 0, + pkt_type: 0, } } @@ -73,6 +81,8 @@ impl NetBuf { csum_ok: false, nfct: NfCt::default(), mark: 0, + priority: 0, + pkt_type: 0, } } @@ -88,6 +98,8 @@ impl NetBuf { csum_ok: false, nfct: NfCt::default(), mark: 0, + priority: 0, + pkt_type: 0, } } diff --git a/net/rk-net/src/netfilter/nf_tables_core.rs b/net/rk-net/src/netfilter/nf_tables_core.rs index 0c5c564..73349b0 100644 --- a/net/rk-net/src/netfilter/nf_tables_core.rs +++ b/net/rk-net/src/netfilter/nf_tables_core.rs @@ -39,6 +39,7 @@ use crate::netfilter::{ nf_pktinfo as nft_pktinfo, }; use crate::netlink::msg::{ATTR_HDRLEN, Attrs, Builder, HDRLEN}; +use crate::packet::PACKET_OTHERHOST; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::sync::Arc; use alloc::vec::Vec; @@ -57,9 +58,10 @@ mod nft_objects; #[cfg(test)] pub(crate) use self::nft_exprs::nft_log_line; use self::nft_exprs::{ - NftByteorder, NftLimit, NftRange, NftReject, nft_byteorder_eval, nft_byteorder_init, - nft_limit_eval, nft_limit_init, nft_log_init, nft_range_eval, nft_range_init, nft_reject_eval, - nft_reject_init, + 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, }; pub(crate) use self::nft_exprs::{NftLog, nft_log_eval}; use self::nft_flowtable::nft_flow_offload_init; @@ -167,6 +169,7 @@ const NFTA_META_KEY: u32 = 2; const NFTA_META_SREG: u32 = 3; const NFT_META_LEN: u32 = 0; const NFT_META_PROTOCOL: u32 = 1; +const NFT_META_PRIORITY: u32 = 2; const NFT_META_MARK: u32 = 3; const NFT_META_IIF: u32 = 4; const NFT_META_OIF: u32 = 5; @@ -176,6 +179,18 @@ const NFT_META_IIFTYPE: u32 = 8; const NFT_META_OIFTYPE: u32 = 9; const NFT_META_NFPROTO: u32 = 15; const NFT_META_L4PROTO: u32 = 16; +const NFT_META_PKTTYPE: u32 = 19; +const NFT_META_CPU: u32 = 20; +const NFT_META_IIFGROUP: u32 = 21; +const NFT_META_OIFGROUP: u32 = 22; +const NFT_META_PRANDOM: u32 = 24; +const NFT_META_IIFKIND: u32 = 26; +const NFT_META_OIFKIND: u32 = 27; +const NFT_META_TIME_NS: u32 = 30; +const NFT_META_TIME_DAY: u32 = 31; +const NFT_META_TIME_HOUR: u32 = 32; +const NFT_META_SDIF: u32 = 33; +const NFT_META_SDIFNAME: u32 = 34; // `NFTA_BITWISE_*` and `enum nft_bitwise_ops`. const NFTA_BITWISE_SREG: u32 = 1; @@ -244,8 +259,8 @@ pub(super) enum NftExpr { Payload { base: u32, offset: usize, len: usize, dreg: usize }, /// `meta` that loads a key of the packet, as `nft_meta_get_eval`. Meta { key: u32, dreg: usize }, - /// `meta mark set`, as `nft_meta_set_eval`. - MetaSet { sreg: usize }, + /// `meta mark set`, `meta priority set` and `meta pkttype set`, as `nft_meta_set_eval`. + MetaSet { key: u32, sreg: usize }, /// `bitwise` with `NFT_BITWISE_BOOL`, as nft_bitwise.c. Bitwise { sreg: usize, dreg: usize, mask: Vec, xor: Vec }, /// `bitwise` with `NFT_BITWISE_LSHIFT` or `NFT_BITWISE_RSHIFT`, as nft_bitwise.c. The @@ -279,6 +294,10 @@ pub(super) enum NftExpr { Byteorder(NftByteorder), /// `range`, as nft_range.c. Range(NftRange), + /// `numgen`, as nft_numgen.c. The copies of the rule share the counter. + Numgen(NftNumgen), + /// `hash`, as nft_hash.c. + Hash(NftHash), /// `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. @@ -569,18 +588,28 @@ pub(super) fn nf_tables_expr_parse( return Err(Errno::EINVAL); } if a.has(NFTA_META_SREG) { - // Of the keys that a rule can set, only the mark is here. - if key != NFT_META_MARK { - return Err(Errno::EOPNOTSUPP); - } - let sreg = nft_parse_register(&a, NFTA_META_SREG, 4)?; - return Ok(NftExpr::MetaSet { sreg }); + // The keys that a rule can set. The kernel has no security marks and no + // trace of the rules. + let len = match key { + NFT_META_MARK | NFT_META_PRIORITY => 4, + NFT_META_PKTTYPE => 1, + _ => return Err(Errno::EOPNOTSUPP), + }; + let sreg = nft_parse_register(&a, NFTA_META_SREG, len)?; + return Ok(NftExpr::MetaSet { key, sreg }); } + // The kernel has no sockets for the packets, no security marks, no classes of the + // routes, no cgroups, no IPsec and no bridges, so the keys of these are not here. let len = match key { - NFT_META_LEN | NFT_META_MARK | NFT_META_IIF | NFT_META_OIF => 4, - NFT_META_IIFNAME | NFT_META_OIFNAME => IFNAMSIZ, NFT_META_PROTOCOL | NFT_META_IIFTYPE | NFT_META_OIFTYPE => 2, - NFT_META_NFPROTO | NFT_META_L4PROTO => 1, + NFT_META_NFPROTO | NFT_META_L4PROTO | NFT_META_LEN | NFT_META_PRIORITY + | NFT_META_MARK | NFT_META_IIF | NFT_META_OIF | NFT_META_SDIF + | NFT_META_PKTTYPE | NFT_META_CPU | NFT_META_IIFGROUP | NFT_META_OIFGROUP + | NFT_META_PRANDOM | NFT_META_TIME_HOUR => 4, + NFT_META_IIFNAME | NFT_META_OIFNAME | NFT_META_IIFKIND | NFT_META_OIFKIND + | NFT_META_SDIFNAME => IFNAMSIZ, + NFT_META_TIME_NS => 8, + NFT_META_TIME_DAY => 1, _ => return Err(Errno::EOPNOTSUPP), }; let dreg = nft_parse_register(&a, NFTA_META_DREG, len)?; @@ -677,6 +706,8 @@ pub(super) fn nf_tables_expr_parse( b"redir" => nft_redir_init(table, data), 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"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)?)), b"connlimit" => nft_connlimit_expr_init(table, data), @@ -865,6 +896,11 @@ fn nft_expr_hooks(e: &NftExpr) -> Option<(u32, bool)> { )), NftExpr::Xt(x) if x.hooks() != 0 => Some((x.hooks(), false)), NftExpr::FlowOffload { .. } => Some((1 << NF_INET_FORWARD, false)), + // As `nft_meta_get_validate_sdif` and `nft_meta_set_validate` of Linux. + NftExpr::Meta { key: NFT_META_SDIF | NFT_META_SDIFNAME, .. } => { + Some((1 << NF_INET_LOCAL_IN | 1 << NF_INET_FORWARD, false)) + } + NftExpr::MetaSet { key: NFT_META_PKTTYPE, .. } => Some((1 << NF_INET_PRE_ROUTING, false)), _ => None, } } @@ -1541,40 +1577,85 @@ fn nft_eval( let p = if state.pf == NFPROTO_IPV6 { ETH_P_IPV6 } else { ETH_P_IP }; regs.store(*dreg, &p.to_be_bytes()); } - NFT_META_IIF if state.in_dev != 0 => { - regs.store(*dreg, &state.in_dev.to_ne_bytes()); - } - NFT_META_OIF if state.out_dev != 0 => { - regs.store(*dreg, &state.out_dev.to_ne_bytes()); - } + // As `nft_meta_store_ifindex`: a packet with no device gives 0. + NFT_META_IIF => regs.store(*dreg, &state.in_dev.to_ne_bytes()), + NFT_META_OIF => regs.store(*dreg, &state.out_dev.to_ne_bytes()), NFT_META_MARK => regs.store(*dreg, &skb.mark.to_ne_bytes()), + NFT_META_PRIORITY => regs.store(*dreg, &skb.priority.to_ne_bytes()), + NFT_META_PKTTYPE => regs.store(*dreg, &[skb.pkt_type]), NFT_META_NFPROTO => regs.store(*dreg, &[state.pf]), NFT_META_L4PROTO if pkt.thoff.is_some() => regs.store(*dreg, &[pkt.l4proto]), - // The name and the type of the device, as `nft_meta_store_ifname` and - // `nft_meta_store_iftype`. A packet with no device does not match. - NFT_META_IIFNAME | NFT_META_OIFNAME | NFT_META_IIFTYPE | NFT_META_OIFTYPE => { + NFT_META_CPU => regs.store(*dreg, &(rk_frame::cpu::id() as u32).to_ne_bytes()), + NFT_META_PRANDOM => regs.store(*dreg, &nft_prandom().to_ne_bytes()), + // As `nft_meta_get_eval_time`, with the time zone of UTC. + NFT_META_TIME_NS => regs.store(*dreg, &nft_realtime_ns().to_ne_bytes()), + NFT_META_TIME_DAY => { + let days = nft_realtime_ns() / 1_000_000_000 / 86_400; + regs.store(*dreg, &[((4 + days) % 7) as u8]); + } + NFT_META_TIME_HOUR => { + let secs = nft_realtime_ns() / 1_000_000_000 % 86_400; + regs.store(*dreg, &(secs as u32).to_ne_bytes()); + } + // The kernel has no devices of VRF, so a packet has no second device, as + // `nft_meta_get_eval_sdif` with no `l3mdev`. + NFT_META_SDIF => regs.store(*dreg, &0u32.to_ne_bytes()), + NFT_META_SDIFNAME => regs.store(*dreg, &[0u8; IFNAMSIZ]), + // The name, the type, the group and the kind of the device, as + // `nft_meta_store_ifname`, `nft_meta_store_iftype`, `nft_meta_store_ifgroup` + // and `nft_meta_get_eval_kind`. A packet with no device has the name "", and + // it does not match the other keys. + NFT_META_IIFNAME | NFT_META_OIFNAME | NFT_META_IIFTYPE | NFT_META_OIFTYPE + | NFT_META_IIFGROUP | NFT_META_OIFGROUP | NFT_META_IIFKIND | NFT_META_OIFKIND => { let ifindex = match *key { - NFT_META_IIFNAME | NFT_META_IIFTYPE => state.in_dev, + NFT_META_IIFNAME | NFT_META_IIFTYPE | NFT_META_IIFGROUP + | NFT_META_IIFKIND => state.in_dev, _ => state.out_dev, }; let dev = if ifindex == 0 { None } else { crate::dev::by_index(ifindex) }; + let mut name = [0u8; IFNAMSIZ]; match (dev, *key) { - (Some(d), NFT_META_IIFNAME | NFT_META_OIFNAME) => { - let mut name = [0u8; IFNAMSIZ]; - let n = d.name(); - let len = n.len().min(IFNAMSIZ); - name[..len].copy_from_slice(&n.as_bytes()[..len]); + (dev, NFT_META_IIFNAME | NFT_META_OIFNAME) => { + if let Some(d) = dev { + let n = d.name(); + let len = n.len().min(IFNAMSIZ); + name[..len].copy_from_slice(&n.as_bytes()[..len]); + } regs.store(*dreg, &name); } + (Some(d), NFT_META_IIFKIND | NFT_META_OIFKIND) => match d.link_kind() { + Some(k) => { + let len = k.len().min(IFNAMSIZ); + name[..len].copy_from_slice(&k.as_bytes()[..len]); + regs.store(*dreg, &name); + } + None => regs.verdict = NFT_BREAK, + }, + // The kernel has no groups of devices: each device is in the group 0, + // `INIT_NETDEV_GROUP`. + (Some(_), NFT_META_IIFGROUP | NFT_META_OIFGROUP) => { + regs.store(*dreg, &0u32.to_ne_bytes()); + } (Some(d), _) => regs.store(*dreg, &d.kind().arphrd().to_ne_bytes()), (None, _) => regs.verdict = NFT_BREAK, } } _ => regs.verdict = NFT_BREAK, }, - NftExpr::MetaSet { sreg } => { + // As `nft_meta_set_eval` of Linux. + NftExpr::MetaSet { key, sreg } => { let r = ®s.data[*sreg..*sreg + 4]; - skb.mark = u32::from_ne_bytes([r[0], r[1], r[2], r[3]]); + let v = u32::from_ne_bytes([r[0], r[1], r[2], r[3]]); + match *key { + NFT_META_MARK => skb.mark = v, + NFT_META_PRIORITY => skb.priority = v, + _ => { + // As `skb_pkt_type_ok`: the types up to `PACKET_OTHERHOST`. + if r[0] <= PACKET_OTHERHOST && skb.pkt_type <= PACKET_OTHERHOST { + skb.pkt_type = r[0]; + } + } + } } NftExpr::Bitwise { sreg, dreg, mask, xor } => { let mut v = [0u8; NFT_REGS_LEN]; @@ -1666,6 +1747,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::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), NftExpr::Connlimit(c) => nft_connlimit_eval(c, regs, skb), @@ -1860,7 +1943,7 @@ mod tests { // meta mark set 5; meta mark 5 drop; meta iifname "lo" drop. let set = vec![ e(NftExpr::Immediate { dreg: R1, data: NftImm::Value(5u32.to_ne_bytes().to_vec()) }), - e(NftExpr::MetaSet { sreg: R1 }), + e(NftExpr::MetaSet { key: NFT_META_MARK, sreg: R1 }), ]; let read = || { vec![ @@ -1894,7 +1977,7 @@ mod tests { }), e(shift(true, 8)), e(shift(false, 4)), - e(NftExpr::MetaSet { sreg: R1 }), + e(NftExpr::MetaSet { key: NFT_META_MARK, sreg: R1 }), ]; let prog = NftProg { chains: vec![vec![set]], sets: vec![], nat: false }; assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut p, &s), NF_ACCEPT); @@ -1920,6 +2003,60 @@ mod tests { assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut udp4(9), &s), NF_ACCEPT); } + #[test] + fn meta_sets_the_priority_and_the_type_and_reads_the_other_keys() { + let s = state(NFPROTO_IPV4); + let imm = |v: &[u8]| e(NftExpr::Immediate { dreg: R1, data: NftImm::Value(v.to_vec()) }); + let is = |key: u32, v: &[u8]| { + [ + e(NftExpr::Meta { key, dreg: R1 }), + e(NftExpr::Cmp { sreg: R1, op: NFT_CMP_EQ, data: v.to_vec() }), + ] + }; + // meta priority set 7; meta pkttype set broadcast. + let mut r = vec![ + imm(&7u32.to_ne_bytes()), + e(NftExpr::MetaSet { key: NFT_META_PRIORITY, sreg: R1 }), + imm(&[1]), + e(NftExpr::MetaSet { key: NFT_META_PKTTYPE, sreg: R1 }), + ]; + r.extend(is(NFT_META_PRIORITY, &7u32.to_ne_bytes())); + r.extend(is(NFT_META_PKTTYPE, &[1])); + // No input device: iif 0 and iifname "". The host has CPU 0 and the time 0, a + // Thursday. + r.extend(is(NFT_META_IIF, &0u32.to_ne_bytes())); + r.extend(is(NFT_META_IIFNAME, &[0; IFNAMSIZ])); + r.extend(is(NFT_META_CPU, &0u32.to_ne_bytes())); + r.extend(is(NFT_META_TIME_DAY, &[4])); + r.extend(is(NFT_META_TIME_HOUR, &0u32.to_ne_bytes())); + r.extend(is(NFT_META_SDIF, &0u32.to_ne_bytes())); + r.push(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); + assert_eq!((p.priority, p.pkt_type), (7, 1)); + // A type over `PACKET_OTHERHOST` does not change the packet. + let r = vec![imm(&[7]), e(NftExpr::MetaSet { key: NFT_META_PKTTYPE, sreg: R1 })]; + let prog = NftProg { chains: vec![vec![r]], sets: vec![], nat: false }; + assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut p, &s), NF_ACCEPT); + assert_eq!(p.pkt_type, 1); + // The group and the kind of a device that is not there do not match. + for key in [NFT_META_IIFGROUP, NFT_META_IIFKIND, NFT_META_IIFTYPE] { + let mut r = vec![e(NftExpr::Meta { key, dreg: R1 })]; + r.push(verdict(NF_DROP, 0)); + let prog = NftProg { chains: vec![vec![r]], sets: vec![], nat: false }; + assert_eq!(nft_do_chain(&prog, NF_ACCEPT, &mut udp4(9), &s), NF_ACCEPT, "{key}"); + } + // meta pkttype set is only in prerouting, and meta sdif only in input and forward. + let set = NftExpr::MetaSet { key: NFT_META_PKTTYPE, sreg: R1 }; + assert_eq!(nft_expr_hooks(&set), Some((1 << NF_INET_PRE_ROUTING, false))); + let sdif = NftExpr::Meta { key: NFT_META_SDIFNAME, dreg: R1 }; + assert_eq!( + nft_expr_hooks(&sdif), + Some((1 << NF_INET_LOCAL_IN | 1 << NF_INET_FORWARD, false)) + ); + } + #[test] fn meta_bitwise_and_lookup_match_the_packet() { let s = state(NFPROTO_IPV4); diff --git a/net/rk-net/src/netfilter/nft_exprs.rs b/net/rk-net/src/netfilter/nft_exprs.rs index 9660f84..a8d0fad 100644 --- a/net/rk-net/src/netfilter/nft_exprs.rs +++ b/net/rk-net/src/netfilter/nft_exprs.rs @@ -1,15 +1,17 @@ -//! The expressions `byteorder`, `range`, `limit`, `log` and `reject` 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 and nft_reject_inet.c of Linux. +//! 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. use super::{NFT_BREAK, NftRegs, be32, be64, len_attr, nft_parse_register, nft_value}; use crate::buf::NetBuf; use crate::netfilter::nf_reject::{NfReject, nf_send_reject}; -use crate::netfilter::{NFPROTO_INET, NFPROTO_IPV4, NFPROTO_IPV6, NfHookState}; +use crate::netfilter::{NFPROTO_INET, NFPROTO_IPV4, NFPROTO_IPV6, NfHookState, NfPktinfo}; use crate::netlink::msg::Attrs; use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::{self, Write}; +use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use rk_abi::Errno; use rk_frame::sync::SpinLock; use rk_vfs::inode::Result; @@ -73,6 +75,25 @@ const ICMPX_V4: [u8; 4] = [0, 3, 1, 13]; /// The ICMPv6 codes of the codes of `NFT_REJECT_ICMPX_UNREACH`, as `icmp_code_v6` of Linux. const ICMPX_V6: [u8; 4] = [0, 4, 3, 1]; +// `NFTA_NG_*` and `enum nft_ng_types`. +const NFTA_NG_DREG: u32 = 1; +const NFTA_NG_MODULUS: u32 = 2; +const NFTA_NG_TYPE: u32 = 3; +const NFTA_NG_OFFSET: u32 = 4; +const NFT_NG_INCREMENTAL: u32 = 0; +const NFT_NG_RANDOM: u32 = 1; + +// `NFTA_HASH_*` and `enum nft_hash_types`. +const NFTA_HASH_SREG: u32 = 1; +const NFTA_HASH_DREG: u32 = 2; +const NFTA_HASH_LEN: u32 = 3; +const NFTA_HASH_MODULUS: u32 = 4; +const NFTA_HASH_SEED: u32 = 5; +const NFTA_HASH_OFFSET: u32 = 6; +const NFTA_HASH_TYPE: u32 = 7; +const NFT_HASH_JENKINS: u32 = 0; +const NFT_HASH_SYM: u32 = 1; + /// `byteorder`, as nft_byteorder.c. #[derive(Clone, Debug)] pub(crate) struct NftByteorder { @@ -551,6 +572,242 @@ pub(crate) fn nft_log_eval(l: &NftLog, skb: &NetBuf, state: &NfHookState) { rk_frame::console::log_emit(l.level, &line); } +/// A pseudo-random number, as `get_random_u32` and `prandom_u32_state` for `numgen random`, +/// `meta random` and the seed of `jhash`. The first call takes a seed from the CPU. +pub(super) fn nft_prandom() -> u32 { + static STATE: AtomicU64 = AtomicU64::new(0); + let mut s = STATE.load(Ordering::Relaxed); + if s == 0 { + s = rk_frame::arch::random_seed() | 1; + } + s ^= s << 13; + s ^= s >> 7; + s ^= s << 17; + STATE.store(s, Ordering::Relaxed); + (s >> 32) as u32 +} + +/// The real time in nanoseconds, as `ktime_get_real_ns` of Linux. The host has no clock. +pub(super) fn nft_realtime_ns() -> u64 { + #[cfg(target_os = "none")] + let ns = rk_frame::time::realtime_ns(); + #[cfg(not(target_os = "none"))] + let ns = 0; + ns +} + +/// Puts `v` in the range from 0 to `n` less 1, as `reciprocal_scale` of Linux. +fn reciprocal_scale(v: u32, n: u32) -> u32 { + ((u64::from(v) * u64::from(n)) >> 32) as u32 +} + +/// The hash of `key` with the seed `initval`, as `jhash` of Linux (lookup3.c of Bob Jenkins). +pub(super) fn jhash(key: &[u8], initval: u32) -> u32 { + let init = 0xdead_beef_u32.wrapping_add(key.len() as u32).wrapping_add(initval); + let (mut a, mut b, mut c) = (init, init, init); + let word = |k: &[u8]| { + let mut w = [0u8; 4]; + w[..k.len()].copy_from_slice(k); + u32::from_le_bytes(w) + }; + // The words of the loop are in the order of the CPU, as `__get_unaligned_cpu32`. + let cpu = |k: &[u8]| u32::from_ne_bytes([k[0], k[1], k[2], k[3]]); + let mut k = key; + while k.len() > 12 { + a = a.wrapping_add(cpu(&k[0..4])); + b = b.wrapping_add(cpu(&k[4..8])); + c = c.wrapping_add(cpu(&k[8..12])); + // `__jhash_mix`. + a = a.wrapping_sub(c) ^ c.rotate_left(4); + c = c.wrapping_add(b); + b = b.wrapping_sub(a) ^ a.rotate_left(6); + a = a.wrapping_add(c); + c = c.wrapping_sub(b) ^ b.rotate_left(8); + b = b.wrapping_add(a); + a = a.wrapping_sub(c) ^ c.rotate_left(16); + c = c.wrapping_add(b); + b = b.wrapping_sub(a) ^ a.rotate_left(19); + a = a.wrapping_add(c); + c = c.wrapping_sub(b) ^ b.rotate_left(4); + b = b.wrapping_add(a); + k = &k[12..]; + } + if k.is_empty() { + return c; + } + // The last bytes, in the order of a little-endian word, as the `switch` of `jhash`. + a = a.wrapping_add(word(&k[..k.len().min(4)])); + if k.len() > 4 { + b = b.wrapping_add(word(&k[4..k.len().min(8)])); + } + if k.len() > 8 { + c = c.wrapping_add(word(&k[8..])); + } + // `__jhash_final`. + c ^= b; + c = c.wrapping_sub(b.rotate_left(14)); + a ^= c; + a = a.wrapping_sub(c.rotate_left(11)); + b ^= a; + b = b.wrapping_sub(a.rotate_left(25)); + c ^= b; + c = c.wrapping_sub(b.rotate_left(16)); + a ^= c; + a = a.wrapping_sub(c.rotate_left(4)); + b ^= a; + b = b.wrapping_sub(a.rotate_left(14)); + c ^= b; + c.wrapping_sub(b.rotate_left(24)) +} + +/// Checks the modulus and the offset of `numgen` and `hash`, as `nft_ng_inc_init` and +/// `nft_jhash_init` of Linux: the last number must not go over `u32::MAX`. +fn nft_modulus_check(modulus: u32, offset: u32) -> Result<()> { + if modulus == 0 { + return Err(Errno::ERANGE); + } + offset.checked_add(modulus - 1).map(|_| ()).ok_or(Errno::EOVERFLOW) +} + +/// `numgen`, as nft_numgen.c: a number from 0 to the modulus less 1, plus the offset. +#[derive(Clone, Debug)] +pub(crate) struct NftNumgen { + dreg: usize, + modulus: u32, + offset: u32, + /// The last number of `NFT_NG_INCREMENTAL`, or `None` for `NFT_NG_RANDOM`. + counter: Option>, +} + +/// Reads a `numgen`, as `nft_ng_select_ops`, `nft_ng_inc_init` and `nft_ng_random_init` of +/// Linux. +pub(super) fn nft_numgen_init(data: &[u8]) -> Result { + let a = Attrs::parse(data, 6); + let (Some(modulus), Some(ty)) = (be32(&a, NFTA_NG_MODULUS)?, be32(&a, NFTA_NG_TYPE)?) else { + return Err(Errno::EINVAL); + }; + if !a.has(NFTA_NG_DREG) || !matches!(ty, NFT_NG_INCREMENTAL | NFT_NG_RANDOM) { + return Err(Errno::EINVAL); + } + let offset = be32(&a, NFTA_NG_OFFSET)?.unwrap_or(0); + nft_modulus_check(modulus, offset)?; + let dreg = nft_parse_register(&a, NFTA_NG_DREG, 4)?; + // The first number of `NFT_NG_INCREMENTAL` is 0. + let counter = (ty == NFT_NG_INCREMENTAL).then(|| Arc::new(AtomicU32::new(modulus - 1))); + Ok(NftNumgen { dreg, modulus, offset, counter }) +} + +/// Puts the next number in the register, as `nft_ng_inc_eval` and `nft_ng_random_eval`. +pub(super) fn nft_numgen_eval(e: &NftNumgen, regs: &mut NftRegs) { + let n = match &e.counter { + Some(c) => { + let next = |o: u32| if o + 1 < e.modulus { o + 1 } else { 0 }; + let old = c + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |o| Some(next(o))) + .unwrap_or_else(|o| o); + next(old) + } + None => reciprocal_scale(nft_prandom(), e.modulus), + }; + regs.store(e.dreg, &(n + e.offset).to_ne_bytes()); +} + +/// `hash`, as nft_hash.c: a hash of a register, or a hash of the flow that is the same in the +/// two directions, from 0 to the modulus less 1, plus the offset. +#[derive(Clone, Debug)] +pub(crate) struct NftHash { + /// The register and the length of the data of `NFT_HASH_JENKINS`, or `None` for + /// `NFT_HASH_SYM`. + src: Option<(usize, usize)>, + dreg: usize, + modulus: u32, + offset: u32, + seed: u32, +} + +/// Reads a `hash`, as `nft_hash_select_ops`, `nft_jhash_init` and `nft_symhash_init` of Linux. +/// A `jhash` with no seed gets a random seed. +pub(super) fn nft_hash_init(data: &[u8]) -> Result { + let a = Attrs::parse(data, 9); + let sym = match be32(&a, NFTA_HASH_TYPE)? { + None | Some(NFT_HASH_JENKINS) => false, + Some(NFT_HASH_SYM) => true, + Some(_) => return Err(Errno::EOPNOTSUPP), + }; + let Some(modulus) = be32(&a, NFTA_HASH_MODULUS)? else { return Err(Errno::EINVAL) }; + if !a.has(NFTA_HASH_DREG) || (!sym && !(a.has(NFTA_HASH_SREG) && a.has(NFTA_HASH_LEN))) { + return Err(Errno::EINVAL); + } + let offset = be32(&a, NFTA_HASH_OFFSET)?.unwrap_or(0); + let mut src = None; + let mut seed = nft_hashrnd(); + if !sym { + let len = match be32(&a, NFTA_HASH_LEN)? { + Some(l @ 1..=255) => l as usize, + _ => return Err(Errno::ERANGE), + }; + src = Some((nft_parse_register(&a, NFTA_HASH_SREG, len)?, len)); + seed = be32(&a, NFTA_HASH_SEED)?.unwrap_or_else(nft_prandom); + } + nft_modulus_check(modulus, offset)?; + let dreg = nft_parse_register(&a, NFTA_HASH_DREG, 4)?; + Ok(NftHash { src, dreg, modulus, offset, seed }) +} + +/// The seed of the hashes of the flows, as `hashrnd` of flow_dissector.c of Linux. It is the +/// same for all of the boot. +fn nft_hashrnd() -> u32 { + static SEED: AtomicU32 = AtomicU32::new(0); + let s = SEED.load(Ordering::Relaxed); + if s != 0 { + return s; + } + let _ = SEED.compare_exchange(0, nft_prandom() | 1, Ordering::Relaxed, Ordering::Relaxed); + SEED.load(Ordering::Relaxed) +} + +/// The hash of the flow of the packet `d` of the family `pf`, as `__skb_get_hash_symmetric` +/// of Linux: the addresses, the ports and the protocol, in an order that does not change with +/// the direction. The hash is not 0. +fn nft_symhash(d: &[u8], pf: u8, pkt: &NfPktinfo, seed: u32) -> u32 { + let (at, alen) = if pf == NFPROTO_IPV6 { (8, 16) } else { (12, 4) }; + let (Some(s), Some(t)) = (d.get(at..at + alen), d.get(at + alen..at + 2 * alen)) else { + return 0; + }; + // TCP, UDP, DCCP, SCTP and UDP-Lite have the ports in the first 4 bytes. + let ports = match (pkt.thoff, pkt.l4proto) { + (Some(off), 6 | 17 | 33 | 132 | 136) => d.get(off..off + 4), + _ => None, + }; + let (sp, tp) = ports.map_or(([0u8; 2], [0u8; 2]), |p| ([p[0], p[1]], [p[2], p[3]])); + let (lo, hi) = if (t, tp) < (s, sp) { ((t, tp), (s, sp)) } else { ((s, sp), (t, tp)) }; + let mut key = [0u8; 37]; + key[..alen].copy_from_slice(lo.0); + key[alen..2 * alen].copy_from_slice(hi.0); + key[2 * alen..2 * alen + 2].copy_from_slice(&lo.1); + key[2 * alen + 2..2 * alen + 4].copy_from_slice(&hi.1); + key[2 * alen + 4] = pkt.l4proto; + match jhash(&key[..2 * alen + 5], seed) { + 0 => 1, + h => h, + } +} + +/// Puts the hash in the register, as `nft_jhash_eval` and `nft_symhash_eval` of Linux. +pub(super) fn nft_hash_eval( + e: &NftHash, + regs: &mut NftRegs, + skb: &NetBuf, + pf: u8, + pkt: &NfPktinfo, +) { + let h = match e.src { + Some((sreg, len)) => jhash(®s.data[sreg..sreg + len], e.seed), + None => nft_symhash(skb.data(), pf, pkt, e.seed), + }; + regs.store(e.dreg, &(reciprocal_scale(h, e.modulus) + e.offset).to_ne_bytes()); +} + #[cfg(test)] mod tests { use super::super::{NFT_CONTINUE, NFT_REG_SIZE}; @@ -579,6 +836,130 @@ mod tests { attrs(&[(1, v)]) } + #[test] + fn jhash_gives_the_values_of_linux() { + let d: Vec = (0..40u8).map(|i| i.wrapping_mul(7) + 1).collect(); + let want = [ + (0, 0xf0e2_1567), + (1, 0x4015_8784), + (3, 0xd8f0_e5df), + (4, 0xf7e6_ac94), + (5, 0x236b_f5cd), + (8, 0xdc12_b871), + (12, 0xe8f4_c145), + (13, 0xa2fb_e119), + (24, 0x56fa_96df), + (25, 0xe5fc_355e), + (40, 0x9d69_5eda), + ]; + for (len, h) in want { + assert_eq!(jhash(&d[..len], 0x1234_5678), h, "{len}"); + } + assert_eq!(jhash(&[10, 0, 0, 1], 0), 0x2fa5_f781); + } + + #[test] + fn numgen_counts_up_to_the_modulus_and_adds_the_offset() { + let be = |v: u32| v.to_be_bytes(); + let ng = |ty: u32, modulus: u32, offset: u32| { + nft_numgen_init(&attrs(&[ + (NFTA_NG_DREG as u16, &be(R1)), + (NFTA_NG_MODULUS as u16, &be(modulus)), + (NFTA_NG_TYPE as u16, &be(ty)), + (NFTA_NG_OFFSET as u16, &be(offset)), + ])) + }; + let n = ng(NFT_NG_INCREMENTAL, 3, 10).unwrap(); + let mut r = regs(); + let mut got = Vec::new(); + for _ in 0..5 { + nft_numgen_eval(&n, &mut r); + got.push(u32::from_ne_bytes( + r.data[NFT_REG_SIZE..NFT_REG_SIZE + 4].try_into().unwrap(), + )); + } + assert_eq!(got, [10, 11, 12, 10, 11]); + // A copy of the rule goes on from the same number. + nft_numgen_eval(&n.clone(), &mut r); + assert_eq!(r.data[NFT_REG_SIZE..NFT_REG_SIZE + 4], 12u32.to_ne_bytes()); + let n = ng(NFT_NG_RANDOM, 4, 0).unwrap(); + for _ in 0..20 { + nft_numgen_eval(&n, &mut r); + assert!( + u32::from_ne_bytes(r.data[NFT_REG_SIZE..NFT_REG_SIZE + 4].try_into().unwrap()) < 4 + ); + } + assert_eq!(ng(NFT_NG_INCREMENTAL, 0, 0).unwrap_err(), Errno::ERANGE); + assert_eq!(ng(NFT_NG_INCREMENTAL, 2, u32::MAX).unwrap_err(), Errno::EOVERFLOW); + assert_eq!(ng(2, 2, 0).unwrap_err(), Errno::EINVAL); + let no_type = attrs(&[(NFTA_NG_DREG as u16, &be(R1)), (NFTA_NG_MODULUS as u16, &be(2))]); + assert_eq!(nft_numgen_init(&no_type).unwrap_err(), Errno::EINVAL); + } + + #[test] + fn hash_puts_a_register_or_the_flow_in_the_modulus() { + let be = |v: u32| v.to_be_bytes(); + // jhash ip saddr mod 10 seed 0x0 offset 100. + let h = nft_hash_init(&attrs(&[ + (NFTA_HASH_SREG as u16, &be(R1)), + (NFTA_HASH_DREG as u16, &be(R1)), + (NFTA_HASH_LEN as u16, &be(4)), + (NFTA_HASH_MODULUS as u16, &be(10)), + (NFTA_HASH_SEED as u16, &be(0)), + (NFTA_HASH_OFFSET as u16, &be(100)), + (NFTA_HASH_TYPE as u16, &be(NFT_HASH_JENKINS)), + ])) + .unwrap(); + let mut r = regs(); + r.data[NFT_REG_SIZE..NFT_REG_SIZE + 4].copy_from_slice(&[10, 0, 0, 1]); + let skb = NetBuf::from_slice(&[0x45; 28]); + let pkt = NfPktinfo { thoff: None, l4proto: 0 }; + nft_hash_eval(&h, &mut r, &skb, NFPROTO_IPV4, &pkt); + let want = reciprocal_scale(0x2fa5_f781, 10) + 100; + assert_eq!(r.data[NFT_REG_SIZE..NFT_REG_SIZE + 4], want.to_ne_bytes()); + // symhash mod 1000: the two directions of a flow get the same number. + let sym = nft_hash_init(&attrs(&[ + (NFTA_HASH_DREG as u16, &be(R1)), + (NFTA_HASH_MODULUS as u16, &be(1000)), + (NFTA_HASH_TYPE as u16, &be(NFT_HASH_SYM)), + ])) + .unwrap(); + let udp = |s: [u8; 4], d: [u8; 4], sp: u16, dp: u16| { + let mut p = [0u8; 28]; + p[0] = 0x45; + p[9] = 17; + p[12..16].copy_from_slice(&s); + p[16..20].copy_from_slice(&d); + p[20..22].copy_from_slice(&sp.to_be_bytes()); + p[22..24].copy_from_slice(&dp.to_be_bytes()); + NetBuf::from_slice(&p) + }; + let pkt = NfPktinfo { thoff: Some(20), l4proto: 17 }; + let mut get = |b: &NetBuf| { + nft_hash_eval(&sym, &mut r, b, NFPROTO_IPV4, &pkt); + u32::from_ne_bytes(r.data[NFT_REG_SIZE..NFT_REG_SIZE + 4].try_into().unwrap()) + }; + let a = get(&udp([10, 0, 0, 1], [10, 0, 0, 2], 1000, 53)); + assert_eq!(a, get(&udp([10, 0, 0, 2], [10, 0, 0, 1], 53, 1000))); + assert!(a < 1000); + let bad = |list: &[(u16, &[u8])]| nft_hash_init(&attrs(list)).unwrap_err(); + let len = |l: u32| { + bad(&[ + (NFTA_HASH_SREG as u16, &be(R1)), + (NFTA_HASH_DREG as u16, &be(R1)), + (NFTA_HASH_LEN as u16, &be(l)), + (NFTA_HASH_MODULUS as u16, &be(10)), + ]) + }; + assert_eq!(len(0), Errno::ERANGE); + assert_eq!(len(256), Errno::ERANGE); + assert_eq!( + bad(&[(NFTA_HASH_DREG as u16, &be(R1)), (NFTA_HASH_MODULUS as u16, &be(10))]), + Errno::EINVAL + ); + assert_eq!(bad(&[(NFTA_HASH_TYPE as u16, &be(2))]), Errno::EOPNOTSUPP); + } + #[test] fn byteorder_turns_each_word() { let be = |v: u32| v.to_be_bytes(); From 145a2ee9dd93ba80682b0ad7f99bb80dc44e95f9 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:41:24 +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 e0617d9..b849b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This file records each release. The format follows [Keep a Changelog](https://ke - The `AF_ALG` sockets with the type `skcipher`, as crypto/algif_skcipher.c of Linux, for `ecb(aes)`, `cbc(aes)`, `ctr(aes)` and `xts(aes)` of `rk-crypto`. A `sendmsg` gives the operation with `ALG_SET_OP` and the IV with `ALG_SET_IV` in control messages, and adds the data to the queue. A `recv` encrypts or decrypts the data of the queue in blocks. With `MSG_MORE`, the operation continues in the next `send`, and the IV goes from one `recv` to the next. libcryptsetup uses these sockets to open a LUKS2 key slot without dm-crypt (#541). - 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). ## [0.5.24] - 2026-11-12 @@ -16,7 +17,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 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). ### Fixed