use std::net::{Ipv4Addr, Ipv6Addr};
use super::{
Connection,
action::ActionList,
builder::MessageBuilder,
connection::{ack_request, create_request, replace_request},
error::{Error, Result},
interface_ref::InterfaceRef,
message::NlMsgType,
protocol::Route,
tc_handle::TcHandle,
types::tc::{
TcMsg, TcaAttr,
filter::{basic, bpf, flower, fw, matchall, u32 as u32_mod},
},
};
const ETH_P_ALL: u16 = 0x0003;
pub trait FilterConfig: Send + Sync {
fn kind(&self) -> &'static str;
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()>;
fn classid(&self) -> Option<u32>;
fn chain(&self) -> Option<u32> {
None
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct U32Filter {
classid: Option<u32>,
keys: Vec<u32_mod::TcU32Key>,
link: Option<u32>,
divisor: Option<u32>,
mark: Option<(u32, u32)>,
priority: u16,
protocol: u16,
chain: Option<u32>,
}
impl U32Filter {
pub fn new() -> Self {
Self {
protocol: 0x0800, priority: 0,
..Default::default()
}
}
pub fn classid(mut self, classid: TcHandle) -> Self {
self.classid = Some(classid.as_raw());
self
}
pub fn priority(mut self, prio: u16) -> Self {
self.priority = prio;
self
}
pub fn protocol(mut self, proto: u16) -> Self {
self.protocol = proto;
self
}
pub fn add_key(mut self, val: u32, mask: u32, off: i32) -> Self {
self.keys.push(u32_mod::pack_key32(val, mask, off));
self
}
pub fn match_src_ipv4(mut self, addr: Ipv4Addr, prefix_len: u8) -> Self {
let mask = if prefix_len >= 32 {
0xFFFFFFFF
} else {
!((1u32 << (32 - prefix_len)) - 1)
};
let val = u32::from_be_bytes(addr.octets());
self.keys.push(u32_mod::pack_key32(val, mask, 12));
self
}
pub fn match_dst_ipv4(mut self, addr: Ipv4Addr, prefix_len: u8) -> Self {
let mask = if prefix_len >= 32 {
0xFFFFFFFF
} else {
!((1u32 << (32 - prefix_len)) - 1)
};
let val = u32::from_be_bytes(addr.octets());
self.keys.push(u32_mod::pack_key32(val, mask, 16));
self
}
pub fn match_ip_proto(mut self, proto: u8) -> Self {
self.keys.push(u32_mod::pack_key8(proto, 0xFF, 9));
self
}
pub fn match_src_port(mut self, port: u16) -> Self {
let key = u32_mod::TcU32Key::with_nexthdr((port as u32) << 16, 0xFFFF0000, 0);
self.keys.push(key);
self
}
pub fn match_dst_port(mut self, port: u16) -> Self {
let key = u32_mod::TcU32Key::with_nexthdr(port as u32, 0x0000FFFF, 0);
self.keys.push(key);
self
}
pub fn match_tos(mut self, tos: u8, mask: u8) -> Self {
self.keys.push(u32_mod::pack_key8(tos, mask, 1));
self
}
pub fn divisor(mut self, div: u32) -> Self {
self.divisor = Some(div);
self
}
pub fn link(mut self, link: u32) -> Self {
self.link = Some(link);
self
}
pub fn match_mark(mut self, val: u32, mask: u32) -> Self {
self.mark = Some((val, mask));
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl FilterConfig for U32Filter {
fn kind(&self) -> &'static str {
"u32"
}
fn classid(&self) -> Option<u32> {
self.classid
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
if let Some(classid) = self.classid {
builder.append_attr_u32(u32_mod::TCA_U32_CLASSID, classid);
}
if let Some(div) = self.divisor {
builder.append_attr_u32(u32_mod::TCA_U32_DIVISOR, div);
}
if let Some(link) = self.link {
builder.append_attr_u32(u32_mod::TCA_U32_LINK, link);
}
if let Some((val, mask)) = self.mark {
let mark = u32_mod::TcU32Mark::new(val, mask);
builder.append_attr(u32_mod::TCA_U32_MARK, mark.as_bytes());
}
if !self.keys.is_empty() {
let mut sel = u32_mod::TcU32Sel::new();
sel.set_terminal();
for key in &self.keys {
sel.add_key(*key);
}
builder.append_attr(u32_mod::TCA_U32_SEL, &sel.to_bytes());
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct FlowerFilter {
classid: Option<u32>,
eth_type: Option<u16>,
ip_proto: Option<u8>,
src_ipv4: Option<(Ipv4Addr, u8)>,
dst_ipv4: Option<(Ipv4Addr, u8)>,
src_ipv6: Option<(Ipv6Addr, u8)>,
dst_ipv6: Option<(Ipv6Addr, u8)>,
src_port: Option<u16>,
dst_port: Option<u16>,
src_mac: Option<[u8; 6]>,
dst_mac: Option<[u8; 6]>,
vlan_id: Option<u16>,
vlan_prio: Option<u8>,
ip_tos: Option<(u8, u8)>,
ip_ttl: Option<(u8, u8)>,
tcp_flags: Option<(u16, u16)>,
flags: u32,
priority: u16,
protocol: u16,
chain: Option<u32>,
goto_chain: Option<u32>,
}
impl FlowerFilter {
pub fn new() -> Self {
Self {
protocol: 0x0003, ..Default::default()
}
}
pub fn classid(mut self, classid: TcHandle) -> Self {
self.classid = Some(classid.as_raw());
self
}
pub fn priority(mut self, prio: u16) -> Self {
self.priority = prio;
self
}
pub fn protocol(mut self, proto: u16) -> Self {
self.protocol = proto;
self
}
pub fn ipv4(mut self) -> Self {
self.eth_type = Some(0x0800);
self
}
pub fn ipv6(mut self) -> Self {
self.eth_type = Some(0x86DD);
self
}
pub fn arp(mut self) -> Self {
self.eth_type = Some(0x0806);
self
}
pub fn ip_proto(mut self, proto: u8) -> Self {
self.ip_proto = Some(proto);
self
}
pub fn ip_proto_tcp(mut self) -> Self {
self.ip_proto = Some(flower::IPPROTO_TCP);
self
}
pub fn ip_proto_udp(mut self) -> Self {
self.ip_proto = Some(flower::IPPROTO_UDP);
self
}
pub fn ip_proto_icmp(mut self) -> Self {
self.ip_proto = Some(flower::IPPROTO_ICMP);
self
}
pub fn ip_proto_icmpv6(mut self) -> Self {
self.ip_proto = Some(flower::IPPROTO_ICMPV6);
self
}
pub fn src_ipv4(mut self, addr: Ipv4Addr, prefix_len: u8) -> Self {
if self.eth_type.is_none() {
self.eth_type = Some(0x0800);
}
self.src_ipv4 = Some((addr, prefix_len));
self
}
pub fn dst_ipv4(mut self, addr: Ipv4Addr, prefix_len: u8) -> Self {
if self.eth_type.is_none() {
self.eth_type = Some(0x0800);
}
self.dst_ipv4 = Some((addr, prefix_len));
self
}
pub fn src_ipv6(mut self, addr: Ipv6Addr, prefix_len: u8) -> Self {
if self.eth_type.is_none() {
self.eth_type = Some(0x86DD);
}
self.src_ipv6 = Some((addr, prefix_len));
self
}
pub fn dst_ipv6(mut self, addr: Ipv6Addr, prefix_len: u8) -> Self {
if self.eth_type.is_none() {
self.eth_type = Some(0x86DD);
}
self.dst_ipv6 = Some((addr, prefix_len));
self
}
pub fn src_port(mut self, port: u16) -> Self {
self.src_port = Some(port);
self
}
pub fn dst_port(mut self, port: u16) -> Self {
self.dst_port = Some(port);
self
}
pub fn src_mac(mut self, mac: [u8; 6]) -> Self {
self.src_mac = Some(mac);
self
}
pub fn dst_mac(mut self, mac: [u8; 6]) -> Self {
self.dst_mac = Some(mac);
self
}
pub fn vlan_id(mut self, id: u16) -> Self {
self.vlan_id = Some(id);
self
}
pub fn vlan_prio(mut self, prio: u8) -> Self {
self.vlan_prio = Some(prio);
self
}
pub fn ip_tos(mut self, tos: u8, mask: u8) -> Self {
self.ip_tos = Some((tos, mask));
self
}
pub fn ip_ttl(mut self, ttl: u8, mask: u8) -> Self {
self.ip_ttl = Some((ttl, mask));
self
}
pub fn tcp_flags(mut self, flags: u16, mask: u16) -> Self {
self.tcp_flags = Some((flags, mask));
self
}
pub fn skip_hw(mut self) -> Self {
self.flags |= flower::TCA_CLS_FLAGS_SKIP_HW;
self
}
pub fn skip_sw(mut self) -> Self {
self.flags |= flower::TCA_CLS_FLAGS_SKIP_SW;
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn goto_chain(mut self, chain: u32) -> Self {
self.goto_chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
fn ipv4_mask(prefix_len: u8) -> Ipv4Addr {
if prefix_len >= 32 {
Ipv4Addr::new(255, 255, 255, 255)
} else if prefix_len == 0 {
Ipv4Addr::new(0, 0, 0, 0)
} else {
let mask = !((1u32 << (32 - prefix_len)) - 1);
Ipv4Addr::from(mask.to_be_bytes())
}
}
fn ipv6_mask(prefix_len: u8) -> Ipv6Addr {
if prefix_len >= 128 {
Ipv6Addr::from([0xFFu8; 16])
} else if prefix_len == 0 {
Ipv6Addr::from([0u8; 16])
} else {
let mut bytes = [0u8; 16];
let full_bytes = (prefix_len / 8) as usize;
let remaining_bits = prefix_len % 8;
for byte in bytes.iter_mut().take(full_bytes) {
*byte = 0xFF;
}
if full_bytes < 16 && remaining_bits > 0 {
bytes[full_bytes] = !((1u8 << (8 - remaining_bits)) - 1);
}
Ipv6Addr::from(bytes)
}
}
impl FilterConfig for FlowerFilter {
fn kind(&self) -> &'static str {
"flower"
}
fn classid(&self) -> Option<u32> {
self.classid
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
if let Some(classid) = self.classid {
builder.append_attr_u32(flower::TCA_FLOWER_CLASSID, classid);
}
if self.flags != 0 {
builder.append_attr_u32(flower::TCA_FLOWER_FLAGS, self.flags);
}
if let Some(eth_type) = self.eth_type {
builder.append_attr(flower::TCA_FLOWER_KEY_ETH_TYPE, ð_type.to_be_bytes());
}
if let Some(proto) = self.ip_proto {
builder.append_attr(flower::TCA_FLOWER_KEY_IP_PROTO, &[proto]);
}
if let Some((addr, prefix_len)) = self.src_ipv4 {
builder.append_attr(flower::TCA_FLOWER_KEY_IPV4_SRC, &addr.octets());
let mask = ipv4_mask(prefix_len);
builder.append_attr(flower::TCA_FLOWER_KEY_IPV4_SRC_MASK, &mask.octets());
}
if let Some((addr, prefix_len)) = self.dst_ipv4 {
builder.append_attr(flower::TCA_FLOWER_KEY_IPV4_DST, &addr.octets());
let mask = ipv4_mask(prefix_len);
builder.append_attr(flower::TCA_FLOWER_KEY_IPV4_DST_MASK, &mask.octets());
}
if let Some((addr, prefix_len)) = self.src_ipv6 {
builder.append_attr(flower::TCA_FLOWER_KEY_IPV6_SRC, &addr.octets());
let mask = ipv6_mask(prefix_len);
builder.append_attr(flower::TCA_FLOWER_KEY_IPV6_SRC_MASK, &mask.octets());
}
if let Some((addr, prefix_len)) = self.dst_ipv6 {
builder.append_attr(flower::TCA_FLOWER_KEY_IPV6_DST, &addr.octets());
let mask = ipv6_mask(prefix_len);
builder.append_attr(flower::TCA_FLOWER_KEY_IPV6_DST_MASK, &mask.octets());
}
if let Some(port) = self.src_port {
if self.ip_proto == Some(flower::IPPROTO_TCP) {
builder.append_attr(flower::TCA_FLOWER_KEY_TCP_SRC, &port.to_be_bytes());
} else if self.ip_proto == Some(flower::IPPROTO_UDP) {
builder.append_attr(flower::TCA_FLOWER_KEY_UDP_SRC, &port.to_be_bytes());
}
}
if let Some(port) = self.dst_port {
if self.ip_proto == Some(flower::IPPROTO_TCP) {
builder.append_attr(flower::TCA_FLOWER_KEY_TCP_DST, &port.to_be_bytes());
} else if self.ip_proto == Some(flower::IPPROTO_UDP) {
builder.append_attr(flower::TCA_FLOWER_KEY_UDP_DST, &port.to_be_bytes());
}
}
if let Some(mac) = self.src_mac {
builder.append_attr(flower::TCA_FLOWER_KEY_ETH_SRC, &mac);
builder.append_attr(flower::TCA_FLOWER_KEY_ETH_SRC_MASK, &[0xFF; 6]);
}
if let Some(mac) = self.dst_mac {
builder.append_attr(flower::TCA_FLOWER_KEY_ETH_DST, &mac);
builder.append_attr(flower::TCA_FLOWER_KEY_ETH_DST_MASK, &[0xFF; 6]);
}
if let Some(id) = self.vlan_id {
builder.append_attr(flower::TCA_FLOWER_KEY_VLAN_ID, &id.to_ne_bytes());
}
if let Some(prio) = self.vlan_prio {
builder.append_attr(flower::TCA_FLOWER_KEY_VLAN_PRIO, &[prio]);
}
if let Some((tos, mask)) = self.ip_tos {
builder.append_attr(flower::TCA_FLOWER_KEY_IP_TOS, &[tos]);
builder.append_attr(flower::TCA_FLOWER_KEY_IP_TOS_MASK, &[mask]);
}
if let Some((ttl, mask)) = self.ip_ttl {
builder.append_attr(flower::TCA_FLOWER_KEY_IP_TTL, &[ttl]);
builder.append_attr(flower::TCA_FLOWER_KEY_IP_TTL_MASK, &[mask]);
}
if let Some((flags, mask)) = self.tcp_flags {
builder.append_attr(flower::TCA_FLOWER_KEY_TCP_FLAGS, &flags.to_be_bytes());
builder.append_attr(flower::TCA_FLOWER_KEY_TCP_FLAGS_MASK, &mask.to_be_bytes());
}
if let Some(chain) = self.goto_chain {
use super::{
action::{ActionConfig, GactAction},
types::tc::{action, filter::flower::TCA_FLOWER_ACT},
};
let goto = GactAction::goto_chain(chain);
let act_token = builder.nest_start(TCA_FLOWER_ACT);
let act1_token = builder.nest_start(1);
builder.append_attr_str(action::TCA_ACT_KIND, goto.kind());
let opt_token = builder.nest_start(action::TCA_ACT_OPTIONS);
goto.write_options(builder)?;
builder.nest_end(opt_token);
builder.nest_end(act1_token);
builder.nest_end(act_token);
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct MatchallFilter {
classid: Option<u32>,
flags: u32,
priority: u16,
protocol: u16,
chain: Option<u32>,
goto_chain: Option<u32>,
}
impl MatchallFilter {
pub fn new() -> Self {
Self {
protocol: 0x0003, ..Default::default()
}
}
pub fn classid(mut self, classid: TcHandle) -> Self {
self.classid = Some(classid.as_raw());
self
}
pub fn priority(mut self, prio: u16) -> Self {
self.priority = prio;
self
}
pub fn protocol(mut self, proto: u16) -> Self {
self.protocol = proto;
self
}
pub fn skip_hw(mut self) -> Self {
self.flags |= flower::TCA_CLS_FLAGS_SKIP_HW;
self
}
pub fn skip_sw(mut self) -> Self {
self.flags |= flower::TCA_CLS_FLAGS_SKIP_SW;
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn goto_chain(mut self, chain: u32) -> Self {
self.goto_chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl FilterConfig for MatchallFilter {
fn kind(&self) -> &'static str {
"matchall"
}
fn classid(&self) -> Option<u32> {
self.classid
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
if let Some(classid) = self.classid {
builder.append_attr_u32(matchall::TCA_MATCHALL_CLASSID, classid);
}
if self.flags != 0 {
builder.append_attr_u32(matchall::TCA_MATCHALL_FLAGS, self.flags);
}
if let Some(chain) = self.goto_chain {
use super::{
action::{ActionConfig, GactAction},
types::tc::action,
};
let goto = GactAction::goto_chain(chain);
let act_token = builder.nest_start(matchall::TCA_MATCHALL_ACT);
let act1_token = builder.nest_start(1);
builder.append_attr_str(action::TCA_ACT_KIND, goto.kind());
let opt_token = builder.nest_start(action::TCA_ACT_OPTIONS);
goto.write_options(builder)?;
builder.nest_end(opt_token);
builder.nest_end(act1_token);
builder.nest_end(act_token);
}
Ok(())
}
}
#[derive(Debug, Clone)]
#[must_use = "builders do nothing unless used"]
pub struct FwFilter {
mask: u32,
classid: Option<u32>,
chain: Option<u32>,
}
impl FwFilter {
pub fn new() -> Self {
Self {
mask: 0xFFFFFFFF,
classid: None,
chain: None,
}
}
pub fn mask(mut self, mask: u32) -> Self {
self.mask = mask;
self
}
pub fn classid(mut self, classid: TcHandle) -> Self {
self.classid = Some(classid.as_raw());
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl Default for FwFilter {
fn default() -> Self {
Self::new()
}
}
impl FilterConfig for FwFilter {
fn kind(&self) -> &'static str {
"fw"
}
fn classid(&self) -> Option<u32> {
self.classid
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
if let Some(classid) = self.classid {
builder.append_attr_u32(fw::TCA_FW_CLASSID, classid);
}
if self.mask != 0xFFFFFFFF {
builder.append_attr_u32(fw::TCA_FW_MASK, self.mask);
}
Ok(())
}
}
#[derive(Debug, Clone)]
#[must_use = "builders do nothing unless used"]
pub struct BpfFilter {
fd: i32,
name: Option<String>,
direct_action: bool,
classid: Option<u32>,
priority: u16,
protocol: u16,
chain: Option<u32>,
}
impl BpfFilter {
pub fn new(fd: i32) -> Self {
Self {
fd,
name: None,
direct_action: false,
classid: None,
priority: 0,
protocol: 0x0003, chain: None,
}
}
pub fn from_pinned(path: impl AsRef<std::path::Path>) -> crate::netlink::Result<Self> {
use std::os::unix::io::IntoRawFd;
let file = std::fs::File::open(path.as_ref())?;
Ok(Self::new(file.into_raw_fd()))
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn direct_action(mut self) -> Self {
self.direct_action = true;
self
}
pub fn classid(mut self, classid: TcHandle) -> Self {
self.classid = Some(classid.as_raw());
self
}
pub fn priority(mut self, prio: u16) -> Self {
self.priority = prio;
self
}
pub fn protocol(mut self, proto: u16) -> Self {
self.protocol = proto;
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl FilterConfig for BpfFilter {
fn kind(&self) -> &'static str {
"bpf"
}
fn classid(&self) -> Option<u32> {
self.classid
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
builder.append_attr_u32(bpf::TCA_BPF_FD, self.fd as u32);
if let Some(ref name) = self.name {
builder.append_attr_str(bpf::TCA_BPF_NAME, name);
}
let mut flags = 0u32;
if self.direct_action {
flags |= bpf::TCA_BPF_FLAG_ACT_DIRECT;
}
if flags != 0 {
builder.append_attr_u32(bpf::TCA_BPF_FLAGS, flags);
}
if let Some(classid) = self.classid {
builder.append_attr_u32(bpf::TCA_BPF_CLASSID, classid);
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct BasicFilter {
classid: Option<u32>,
priority: u16,
protocol: u16,
chain: Option<u32>,
}
impl BasicFilter {
pub fn new() -> Self {
Self {
protocol: 0x0003, ..Default::default()
}
}
pub fn classid(mut self, classid: TcHandle) -> Self {
self.classid = Some(classid.as_raw());
self
}
pub fn priority(mut self, prio: u16) -> Self {
self.priority = prio;
self
}
pub fn protocol(mut self, proto: u16) -> Self {
self.protocol = proto;
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl FilterConfig for BasicFilter {
fn kind(&self) -> &'static str {
"basic"
}
fn classid(&self) -> Option<u32> {
self.classid
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
if let Some(classid) = self.classid {
builder.append_attr_u32(basic::TCA_BASIC_CLASSID, classid);
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct CgroupFilter {
actions: Option<super::action::ActionList>,
chain: Option<u32>,
}
impl CgroupFilter {
pub fn new() -> Self {
Self::default()
}
pub fn with_action<A: super::action::ActionConfig + Clone + std::fmt::Debug + 'static>(
mut self,
action: A,
) -> Self {
let actions = self.actions.take().unwrap_or_default().with(action);
self.actions = Some(actions);
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl FilterConfig for CgroupFilter {
fn kind(&self) -> &'static str {
"cgroup"
}
fn classid(&self) -> Option<u32> {
None }
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::filter::cgroup;
if let Some(ref actions) = self.actions {
let act_token = builder.nest_start(cgroup::TCA_CGROUP_ACT);
actions.write_to(builder)?;
builder.nest_end(act_token);
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "builders do nothing unless used"]
pub struct RouteFilter {
classid: Option<u32>,
to_realm: Option<u32>,
from_realm: Option<u32>,
from_if: Option<InterfaceRef>,
actions: Option<super::action::ActionList>,
chain: Option<u32>,
}
impl RouteFilter {
pub fn new() -> Self {
Self::default()
}
pub fn classid(mut self, classid: TcHandle) -> Self {
self.classid = Some(classid.as_raw());
self
}
pub fn to_realm(mut self, realm: u32) -> Self {
self.to_realm = Some(realm);
self
}
pub fn from_realm(mut self, realm: u32) -> Self {
self.from_realm = Some(realm);
self
}
pub fn from_if(mut self, dev: impl Into<String>) -> Self {
self.from_if = Some(InterfaceRef::Name(dev.into()));
self
}
pub fn from_if_index(mut self, ifindex: u32) -> Self {
self.from_if = Some(InterfaceRef::Index(ifindex));
self
}
pub fn from_if_ref(&self) -> Option<&InterfaceRef> {
self.from_if.as_ref()
}
pub fn with_action<A: super::action::ActionConfig + Clone + std::fmt::Debug + 'static>(
mut self,
action: A,
) -> Self {
let actions = self.actions.take().unwrap_or_default().with(action);
self.actions = Some(actions);
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl FilterConfig for RouteFilter {
fn kind(&self) -> &'static str {
"route"
}
fn classid(&self) -> Option<u32> {
self.classid
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::filter::route4;
if let Some(classid) = self.classid {
builder.append_attr_u32(route4::TCA_ROUTE4_CLASSID, classid);
}
if let Some(realm) = self.to_realm {
builder.append_attr_u32(route4::TCA_ROUTE4_TO, realm);
}
if let Some(realm) = self.from_realm {
builder.append_attr_u32(route4::TCA_ROUTE4_FROM, realm);
}
if let Some(ref iface) = self.from_if {
let ifindex = match iface {
InterfaceRef::Index(idx) => *idx,
InterfaceRef::Name(name) => {
return Err(Error::InvalidMessage(format!(
"RouteFilter from_if interface '{}' must be resolved to index before use. \
Use from_if_index() or resolve the name via Connection::get_link_by_name()",
name
)));
}
};
builder.append_attr_u32(route4::TCA_ROUTE4_IIF, ifindex);
}
if let Some(ref actions) = self.actions {
let act_token = builder.nest_start(route4::TCA_ROUTE4_ACT);
actions.write_to(builder)?;
builder.nest_end(act_token);
}
Ok(())
}
}
#[derive(Debug, Clone)]
#[must_use = "builders do nothing unless used"]
pub struct FlowFilter {
keys: u32,
mode: u32,
baseclass: Option<u32>,
rshift: Option<u32>,
addend: Option<u32>,
mask: Option<u32>,
xor: Option<u32>,
divisor: Option<u32>,
perturb: Option<u32>,
priority: u16,
protocol: u16,
actions: Option<ActionList>,
chain: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlowKey {
Src,
Dst,
Proto,
ProtoSrc,
ProtoDst,
Iif,
Priority,
Mark,
Nfct,
NfctSrc,
NfctDst,
NfctProtoSrc,
NfctProtoDst,
RtClassid,
SkUid,
SkGid,
VlanTag,
RxHash,
}
impl FlowKey {
fn to_bit(self) -> u32 {
use super::types::tc::filter::flow;
match self {
FlowKey::Src => flow::FLOW_KEY_SRC,
FlowKey::Dst => flow::FLOW_KEY_DST,
FlowKey::Proto => flow::FLOW_KEY_PROTO,
FlowKey::ProtoSrc => flow::FLOW_KEY_PROTO_SRC,
FlowKey::ProtoDst => flow::FLOW_KEY_PROTO_DST,
FlowKey::Iif => flow::FLOW_KEY_IIF,
FlowKey::Priority => flow::FLOW_KEY_PRIORITY,
FlowKey::Mark => flow::FLOW_KEY_MARK,
FlowKey::Nfct => flow::FLOW_KEY_NFCT,
FlowKey::NfctSrc => flow::FLOW_KEY_NFCT_SRC,
FlowKey::NfctDst => flow::FLOW_KEY_NFCT_DST,
FlowKey::NfctProtoSrc => flow::FLOW_KEY_NFCT_PROTO_SRC,
FlowKey::NfctProtoDst => flow::FLOW_KEY_NFCT_PROTO_DST,
FlowKey::RtClassid => flow::FLOW_KEY_RTCLASSID,
FlowKey::SkUid => flow::FLOW_KEY_SKUID,
FlowKey::SkGid => flow::FLOW_KEY_SKGID,
FlowKey::VlanTag => flow::FLOW_KEY_VLAN_TAG,
FlowKey::RxHash => flow::FLOW_KEY_RXHASH,
}
}
}
impl Default for FlowFilter {
fn default() -> Self {
Self::new()
}
}
impl FlowFilter {
pub fn new() -> Self {
use super::types::tc::filter::flow;
Self {
keys: 0,
mode: flow::FLOW_MODE_MAP,
baseclass: None,
rshift: None,
addend: None,
mask: None,
xor: None,
divisor: None,
perturb: None,
priority: 0,
protocol: ETH_P_ALL,
actions: None,
chain: None,
}
}
pub fn key(mut self, key: FlowKey) -> Self {
self.keys |= key.to_bit();
self
}
pub fn keys(mut self, keys: &[FlowKey]) -> Self {
for key in keys {
self.keys |= key.to_bit();
}
self
}
pub fn mode_map(mut self) -> Self {
use super::types::tc::filter::flow;
self.mode = flow::FLOW_MODE_MAP;
self
}
pub fn mode_hash(mut self) -> Self {
use super::types::tc::filter::flow;
self.mode = flow::FLOW_MODE_HASH;
self
}
pub fn baseclass(mut self, classid: TcHandle) -> Self {
self.baseclass = Some(classid.as_raw());
self
}
pub fn rshift(mut self, shift: u32) -> Self {
self.rshift = Some(shift);
self
}
pub fn addend(mut self, addend: u32) -> Self {
self.addend = Some(addend);
self
}
pub fn mask(mut self, mask: u32) -> Self {
self.mask = Some(mask);
self
}
pub fn xor(mut self, xor: u32) -> Self {
self.xor = Some(xor);
self
}
pub fn divisor(mut self, divisor: u32) -> Self {
self.divisor = Some(divisor);
self
}
pub fn perturb(mut self, seconds: u32) -> Self {
self.perturb = Some(seconds);
self
}
pub fn priority(mut self, priority: u16) -> Self {
self.priority = priority;
self
}
pub fn protocol(mut self, protocol: u16) -> Self {
self.protocol = protocol;
self
}
pub fn actions(mut self, actions: ActionList) -> Self {
self.actions = Some(actions);
self
}
pub fn chain(mut self, chain: u32) -> Self {
self.chain = Some(chain);
self
}
pub fn build(self) -> Self {
self
}
}
impl FilterConfig for FlowFilter {
fn kind(&self) -> &'static str {
"flow"
}
fn classid(&self) -> Option<u32> {
self.baseclass
}
fn chain(&self) -> Option<u32> {
self.chain
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::filter::flow;
if self.keys != 0 {
builder.append_attr_u32(flow::TCA_FLOW_KEYS, self.keys);
}
builder.append_attr_u32(flow::TCA_FLOW_MODE, self.mode);
if let Some(baseclass) = self.baseclass {
builder.append_attr_u32(flow::TCA_FLOW_BASECLASS, baseclass);
}
if let Some(rshift) = self.rshift {
builder.append_attr_u32(flow::TCA_FLOW_RSHIFT, rshift);
}
if let Some(addend) = self.addend {
builder.append_attr_u32(flow::TCA_FLOW_ADDEND, addend);
}
if let Some(mask) = self.mask {
builder.append_attr_u32(flow::TCA_FLOW_MASK, mask);
}
if let Some(xor) = self.xor {
builder.append_attr_u32(flow::TCA_FLOW_XOR, xor);
}
if let Some(divisor) = self.divisor {
builder.append_attr_u32(flow::TCA_FLOW_DIVISOR, divisor);
}
if let Some(perturb) = self.perturb {
builder.append_attr_u32(flow::TCA_FLOW_PERTURB, perturb);
}
if let Some(ref actions) = self.actions {
let act_token = builder.nest_start(flow::TCA_FLOW_ACT);
actions.write_to(builder)?;
builder.nest_end(act_token);
}
Ok(())
}
}
impl Connection<Route> {
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_filter"))]
pub async fn add_filter(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
config: impl FilterConfig,
) -> Result<()> {
self.add_filter_full(dev, parent, None, 0x0800, 0, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_filter_full"))]
pub async fn add_filter_full(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
handle: Option<TcHandle>,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.add_filter_by_index_full(ifindex, parent, handle, protocol, priority, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_filter_by_index"))]
pub async fn add_filter_by_index(
&self,
ifindex: u32,
parent: TcHandle,
config: impl FilterConfig,
) -> Result<()> {
self.add_filter_by_index_full(ifindex, parent, None, 0x0800, 0, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_filter_by_index_full"))]
pub async fn add_filter_by_index_full(
&self,
ifindex: u32,
parent: TcHandle,
handle: Option<TcHandle>,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
let parent_handle = parent.as_raw();
let filter_handle = handle.map(|h| h.as_raw()).unwrap_or(0);
let info = ((protocol as u32) << 16) | (priority as u32);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(filter_handle)
.with_info(info);
let mut builder = create_request(NlMsgType::RTM_NEWTFILTER);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
if let Some(chain) = config.chain() {
builder.append_attr_u32(TcaAttr::Chain as u16, chain);
}
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("add_filter"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_filter"))]
pub async fn replace_filter(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
config: impl FilterConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.replace_filter_by_index_full(ifindex, parent, None, 0x0800, 0, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_filter_full"))]
pub async fn replace_filter_full(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
handle: Option<TcHandle>,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.replace_filter_by_index_full(ifindex, parent, handle, protocol, priority, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_filter_by_index"))]
pub async fn replace_filter_by_index(
&self,
ifindex: u32,
parent: TcHandle,
config: impl FilterConfig,
) -> Result<()> {
self.replace_filter_by_index_full(ifindex, parent, None, 0x0800, 0, config)
.await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "replace_filter_by_index_full")
)]
pub async fn replace_filter_by_index_full(
&self,
ifindex: u32,
parent: TcHandle,
handle: Option<TcHandle>,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
let parent_handle = parent.as_raw();
let filter_handle = handle.map(|h| h.as_raw()).unwrap_or(0);
let info = ((protocol as u32) << 16) | (priority as u32);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(filter_handle)
.with_info(info);
let mut builder = replace_request(NlMsgType::RTM_NEWTFILTER);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
if let Some(chain) = config.chain() {
builder.append_attr_u32(TcaAttr::Chain as u16, chain);
}
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("replace_filter"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_filter"))]
pub async fn change_filter(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.change_filter_by_index_full(ifindex, parent, None, protocol, priority, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_filter_full"))]
pub async fn change_filter_full(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
handle: Option<TcHandle>,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.change_filter_by_index_full(ifindex, parent, handle, protocol, priority, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_filter_by_index"))]
pub async fn change_filter_by_index(
&self,
ifindex: u32,
parent: TcHandle,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
self.change_filter_by_index_full(ifindex, parent, None, protocol, priority, config)
.await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "change_filter_by_index_full")
)]
pub async fn change_filter_by_index_full(
&self,
ifindex: u32,
parent: TcHandle,
handle: Option<TcHandle>,
protocol: u16,
priority: u16,
config: impl FilterConfig,
) -> Result<()> {
let parent_handle = parent.as_raw();
let filter_handle = handle.map(|h| h.as_raw()).unwrap_or(0);
let info = ((protocol as u32) << 16) | (priority as u32);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(filter_handle)
.with_info(info);
let mut builder = ack_request(NlMsgType::RTM_NEWTFILTER);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
if let Some(chain) = config.chain() {
builder.append_attr_u32(TcaAttr::Chain as u16, chain);
}
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("change_filter"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_filter"))]
pub async fn del_filter(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
protocol: u16,
priority: u16,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.del_filter_by_index(ifindex, parent, protocol, priority)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_filter_by_index"))]
pub async fn del_filter_by_index(
&self,
ifindex: u32,
parent: TcHandle,
protocol: u16,
priority: u16,
) -> Result<()> {
let parent_handle = parent.as_raw();
let info = ((protocol as u32) << 16) | (priority as u32);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_info(info);
let mut builder = create_request(NlMsgType::RTM_DELTFILTER);
builder.append(&tcmsg);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_filter"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "flush_filters"))]
pub async fn flush_filters(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.flush_filters_by_index(ifindex, parent).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "flush_filters_by_index"))]
pub async fn flush_filters_by_index(&self, ifindex: u32, parent: TcHandle) -> Result<()> {
let filters = self.get_filters().await?;
for filter in filters {
if filter.ifindex() == ifindex && filter.parent() == parent {
let protocol = filter.protocol();
let priority = filter.priority();
if let Err(e) = self
.del_filter_by_index(ifindex, parent, protocol, priority)
.await
{
if !e.is_not_found() {
return Err(e);
}
}
}
}
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "attach_bpf"))]
pub async fn attach_bpf(
&self,
dev: impl Into<InterfaceRef>,
direction: BpfDirection,
filter: BpfFilter,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.attach_bpf_by_index(ifindex, direction, filter).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "attach_bpf_by_index"))]
pub async fn attach_bpf_by_index(
&self,
ifindex: u32,
direction: BpfDirection,
filter: BpfFilter,
) -> Result<()> {
match self
.add_qdisc_by_index(ifindex, crate::netlink::tc::ClsactConfig::new())
.await
{
Ok(()) => {}
Err(e) if e.is_already_exists() => {}
Err(e) => return Err(e),
}
let parent = match direction {
BpfDirection::Ingress => TcHandle::CLSACT,
BpfDirection::Egress => TcHandle::from_raw(0xFFFF_FFF3),
};
self.add_filter_by_index(ifindex, parent, filter).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "detach_bpf"))]
pub async fn detach_bpf(
&self,
dev: impl Into<InterfaceRef>,
direction: BpfDirection,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.detach_bpf_by_index(ifindex, direction).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "detach_bpf_by_index"))]
pub async fn detach_bpf_by_index(&self, ifindex: u32, direction: BpfDirection) -> Result<()> {
let parent = match direction {
BpfDirection::Ingress => TcHandle::CLSACT,
BpfDirection::Egress => TcHandle::from_raw(0xFFFF_FFF3),
};
self.flush_filters_by_index(ifindex, parent).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "list_bpf_programs"))]
pub async fn list_bpf_programs(
&self,
dev: impl Into<InterfaceRef>,
) -> Result<Vec<crate::netlink::messages::BpfInfo>> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.list_bpf_programs_by_index(ifindex).await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "list_bpf_programs_by_index")
)]
pub async fn list_bpf_programs_by_index(
&self,
ifindex: u32,
) -> Result<Vec<crate::netlink::messages::BpfInfo>> {
let mut programs = Vec::new();
let all_filters = match self.get_filters_by_index(ifindex).await {
Ok(f) => f,
Err(e) if e.is_not_found() || e.is_invalid_argument() => {
return Ok(programs);
}
Err(e) => return Err(e),
};
for filter in &all_filters {
if let Some(info) = filter.bpf_info() {
programs.push(info);
}
}
Ok(programs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BpfDirection {
Ingress,
Egress,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_u32_filter_builder() {
let filter = U32Filter::new()
.classid(TcHandle::new(1, 0x10))
.match_dst_ipv4(Ipv4Addr::new(192, 168, 1, 0), 24)
.priority(100)
.build();
assert_eq!(filter.classid, Some(TcHandle::new(1, 0x10).as_raw()));
assert_eq!(filter.priority, 100);
assert_eq!(filter.keys.len(), 1);
}
#[test]
fn test_flower_filter_builder() {
let filter = FlowerFilter::new()
.classid(TcHandle::new(1, 0x20))
.ip_proto_tcp()
.dst_ipv4(Ipv4Addr::new(10, 0, 0, 0), 8)
.dst_port(80)
.build();
assert_eq!(filter.classid, Some(TcHandle::new(1, 0x20).as_raw()));
assert_eq!(filter.ip_proto, Some(flower::IPPROTO_TCP));
assert_eq!(filter.dst_ipv4, Some((Ipv4Addr::new(10, 0, 0, 0), 8)));
assert_eq!(filter.dst_port, Some(80));
assert_eq!(filter.eth_type, Some(0x0800));
}
#[test]
fn test_matchall_filter_builder() {
let filter = MatchallFilter::new()
.classid(TcHandle::new(1, 0x30))
.skip_hw()
.build();
assert_eq!(filter.classid, Some(TcHandle::new(1, 0x30).as_raw()));
assert!(filter.flags & flower::TCA_CLS_FLAGS_SKIP_HW != 0);
}
#[test]
fn test_fw_filter_builder() {
let filter = FwFilter::new()
.classid(TcHandle::new(1, 0x10))
.mask(0xFF)
.build();
assert_eq!(filter.mask, 0xFF);
assert_eq!(filter.classid, Some(TcHandle::new(1, 0x10).as_raw()));
}
#[test]
fn test_ipv4_mask() {
assert_eq!(ipv4_mask(32), Ipv4Addr::new(255, 255, 255, 255));
assert_eq!(ipv4_mask(24), Ipv4Addr::new(255, 255, 255, 0));
assert_eq!(ipv4_mask(16), Ipv4Addr::new(255, 255, 0, 0));
assert_eq!(ipv4_mask(8), Ipv4Addr::new(255, 0, 0, 0));
assert_eq!(ipv4_mask(0), Ipv4Addr::new(0, 0, 0, 0));
}
#[test]
fn test_ipv6_mask() {
let full = ipv6_mask(128);
assert_eq!(full.octets(), [0xFF; 16]);
let zero = ipv6_mask(0);
assert_eq!(zero.octets(), [0; 16]);
let half = ipv6_mask(64);
assert_eq!(
half.octets(),
[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0
]
);
}
#[test]
fn test_cgroup_filter_builder() {
let filter = CgroupFilter::new().build();
assert_eq!(FilterConfig::kind(&filter), "cgroup");
assert_eq!(filter.classid(), None);
}
#[test]
fn test_route_filter_builder() {
let filter = RouteFilter::new()
.to_realm(10)
.from_realm(5)
.classid(TcHandle::new(1, 0x10))
.build();
assert_eq!(FilterConfig::kind(&filter), "route");
assert_eq!(filter.to_realm, Some(10));
assert_eq!(filter.from_realm, Some(5));
assert_eq!(filter.classid, Some(TcHandle::new(1, 0x10).as_raw()));
}
#[test]
fn test_flow_filter_builder() {
use crate::netlink::types::tc::filter::flow;
let filter = FlowFilter::new()
.keys(&[FlowKey::Src, FlowKey::Dst])
.mode_hash()
.divisor(256)
.baseclass(TcHandle::new(1, 0x10))
.build();
assert_eq!(FilterConfig::kind(&filter), "flow");
assert_eq!(filter.keys, flow::FLOW_KEY_SRC | flow::FLOW_KEY_DST);
assert_eq!(filter.mode, flow::FLOW_MODE_HASH);
assert_eq!(filter.divisor, Some(256));
assert_eq!(filter.baseclass, Some(TcHandle::new(1, 0x10).as_raw()));
let filter = FlowFilter::new()
.key(FlowKey::Mark)
.mode_map()
.mask(0xff)
.rshift(8)
.build();
assert_eq!(filter.keys, flow::FLOW_KEY_MARK);
assert_eq!(filter.mode, flow::FLOW_MODE_MAP);
assert_eq!(filter.mask, Some(0xff));
assert_eq!(filter.rshift, Some(8));
}
#[test]
fn test_bpf_filter_builder() {
let filter = BpfFilter::new(42)
.name("my_prog")
.direct_action()
.priority(100)
.chain(5);
assert_eq!(filter.fd, 42);
assert_eq!(filter.name.as_deref(), Some("my_prog"));
assert!(filter.direct_action);
assert_eq!(filter.priority, 100);
assert_eq!(filter.chain, Some(5));
}
#[test]
fn test_bpf_filter_defaults() {
let filter = BpfFilter::new(7);
assert_eq!(filter.fd, 7);
assert!(filter.name.is_none());
assert!(!filter.direct_action);
assert_eq!(filter.priority, 0);
assert_eq!(filter.protocol, 3); assert!(filter.chain.is_none());
assert!(filter.classid.is_none());
}
#[test]
fn test_bpf_from_pinned_invalid_path() {
let result = BpfFilter::from_pinned("/nonexistent/path/to/bpf");
assert!(result.is_err());
}
}