use std::net::Ipv4Addr;
use nlink::netlink::nftables::config::NftablesConfig;
use nlink::netlink::nftables::types::{Family, Hook, Policy, Priority, SetKeyType};
use nlink::netlink::{Connection, Nftables};
#[tokio::main]
async fn main() -> nlink::Result<()> {
let conn = Connection::<Nftables>::new()?;
let cfg = NftablesConfig::new().table("filter_demo", Family::Inet, |t| {
t.chain("input", |c| {
c.hook(Hook::Input)
.priority(Priority::Filter)
.policy(Policy::Drop)
})
.rule_keyed("input", "ssh-allow", |r| r.match_tcp_dport(22).accept())
.rule_keyed("input", "icmp-allow", |r| {
r.match_l4proto(1 ).accept()
})
.set("allowed_v4", |s| {
s.key_type(SetKeyType::Ipv4Addr)
.ipv4(Ipv4Addr::new(10, 0, 0, 1))
.ipv4(Ipv4Addr::new(10, 0, 0, 2))
})
});
let diff = match cfg.diff(&conn).await {
Ok(d) => d,
Err(e) if e.is_permission_denied() => {
eprintln!(
"EPERM: nftables config diff requires CAP_NET_ADMIN; \
re-run with sudo (rule integrity end-to-end demo)",
);
return Ok(());
}
Err(e) => return Err(e),
};
println!("initial diff:\n{}", diff);
let applied = diff.apply(&conn).await?;
println!("\napplied {applied} ops\n");
let reapply_diff = cfg.diff(&conn).await?;
assert!(
reapply_diff.is_empty(),
"idempotent re-apply should produce empty diff; got: {}",
reapply_diff,
);
println!("reapply diff: {} (empty — idempotent ✓)\n", reapply_diff);
let updated = NftablesConfig::new().table("filter_demo", Family::Inet, |t| {
t.chain("input", |c| {
c.hook(Hook::Input)
.priority(Priority::Filter)
.policy(Policy::Drop)
})
.rule_keyed("input", "ssh-allow", |r| r.match_tcp_dport(2222).accept()) .rule_keyed("input", "icmp-allow", |r| r.match_l4proto(1).accept())
.set("allowed_v4", |s| {
s.key_type(SetKeyType::Ipv4Addr)
.ipv4(Ipv4Addr::new(10, 0, 0, 1))
.ipv4(Ipv4Addr::new(10, 0, 0, 2))
.ipv4(Ipv4Addr::new(10, 0, 0, 3)) })
});
let mut_diff = updated.diff(&conn).await?;
println!("after port change + new set element:\n{}", mut_diff);
assert_eq!(
mut_diff.rules_to_replace.len(),
1,
"expected exactly one in-place replace for the changed rule",
);
assert_eq!(
mut_diff.set_elements_to_add.len(),
1,
"expected exactly one set_elements_to_add op (only the new key)",
);
assert_eq!(
mut_diff.set_elements_to_add[0].3.len(),
1,
"element diff should add just the single new key, not the whole set",
);
let applied = mut_diff.apply(&conn).await?;
println!("\napplied {applied} ops (1 replace_rule via NLM_F_REPLACE + 1 set-element add)\n");
let teardown = NftablesConfig::new(); let drop_diff = teardown.diff(&conn).await?;
println!("teardown diff:\n{}", drop_diff);
let applied = drop_diff.apply(&conn).await?;
println!("\nteardown applied {applied} ops — demo complete");
Ok(())
}