use ipnetwork::{IpNetwork, Ipv4Network};
use nftnl::{Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table, nft_expr, nftnl_sys::libc};
use std::{ffi::CStr, io, net::Ipv4Addr};
const TABLE_NAME: &CStr = c"example-table";
const OUT_CHAIN_NAME: &CStr = c"chain-for-outgoing-packets";
const IN_CHAIN_NAME: &CStr = c"chain-for-incoming-packets";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut batch = Batch::new();
let table = Table::new(TABLE_NAME, ProtoFamily::Inet);
batch.add(&table, nftnl::MsgType::Add);
let mut out_chain = Chain::new(OUT_CHAIN_NAME, &table);
let mut in_chain = Chain::new(IN_CHAIN_NAME, &table);
out_chain.set_hook(nftnl::Hook::Out, 0);
in_chain.set_hook(nftnl::Hook::In, 0);
out_chain.set_policy(nftnl::Policy::Accept);
in_chain.set_policy(nftnl::Policy::Accept);
batch.add(&out_chain, nftnl::MsgType::Add);
batch.add(&in_chain, nftnl::MsgType::Add);
let mut allow_loopback_in_rule = Rule::new(&in_chain);
let lo_iface_index = iface_index(c"lo")?;
allow_loopback_in_rule.add_expr(&nft_expr!(meta iif));
allow_loopback_in_rule.add_expr(&nft_expr!(cmp == lo_iface_index));
allow_loopback_in_rule.add_expr(&nft_expr!(verdict accept));
batch.add(&allow_loopback_in_rule, nftnl::MsgType::Add);
let mut block_out_to_private_net_rule = Rule::new(&out_chain);
let private_net_ip = Ipv4Addr::new(10, 1, 0, 0);
let private_net_prefix = 24;
let private_net = IpNetwork::V4(Ipv4Network::new(private_net_ip, private_net_prefix)?);
block_out_to_private_net_rule.add_expr(&nft_expr!(meta nfproto));
block_out_to_private_net_rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV4 as u8));
block_out_to_private_net_rule.add_expr(&nft_expr!(payload ipv4 daddr));
block_out_to_private_net_rule.add_expr(&nft_expr!(bitwise mask private_net.mask(), xor 0));
block_out_to_private_net_rule.add_expr(&nft_expr!(cmp == private_net.ip()));
block_out_to_private_net_rule.add_expr(&nft_expr!(counter));
block_out_to_private_net_rule.add_expr(&nft_expr!(verdict accept));
batch.add(&block_out_to_private_net_rule, nftnl::MsgType::Add);
let mut allow_router_solicitation = Rule::new(&out_chain);
allow_router_solicitation.add_expr(&nft_expr!(meta nfproto));
allow_router_solicitation.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV6 as u8));
allow_router_solicitation.add_expr(&nft_expr!(meta l4proto));
allow_router_solicitation.add_expr(&nft_expr!(cmp == libc::IPPROTO_ICMPV6 as u8));
allow_router_solicitation.add_expr(&nftnl::expr::Payload::Transport(
nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Type),
));
allow_router_solicitation.add_expr(&nft_expr!(cmp == 133u8));
allow_router_solicitation.add_expr(&nftnl::expr::Payload::Transport(
nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Code),
));
allow_router_solicitation.add_expr(&nft_expr!(cmp == 0u8));
allow_router_solicitation.add_expr(&nft_expr!(verdict accept));
batch.add(&allow_router_solicitation, nftnl::MsgType::Add);
let finalized_batch = batch.finalize();
send_and_process(&finalized_batch)?;
Ok(())
}
fn iface_index(name: &CStr) -> io::Result<libc::c_uint> {
let index = unsafe { libc::if_nametoindex(name.as_ptr()) };
if index == 0 {
Err(io::Error::last_os_error())
} else {
Ok(index)
}
}
fn send_and_process(batch: &FinalizedBatch) -> io::Result<()> {
let socket = mnl::Socket::new(mnl::Bus::Netfilter)?;
let portid = socket.portid();
socket.send_all(batch)?;
let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize];
let mut expected_seqs = batch.sequence_numbers();
while !expected_seqs.is_empty() {
for message in socket.recv(&mut buffer[..])? {
let message = message?;
let expected_seq = expected_seqs.next().expect("Unexpected ACK");
mnl::cb_run(message, expected_seq, portid)?;
}
}
Ok(())
}