//! Adds a table, chain and a rule that blocks all traffic to a given MAC address //! //! Run the following to print out current active tables, chains and rules in netfilter. Must be //! executed as root: //! ```bash //! # nft list ruleset //! ``` //! After running this example, the output should be the following: //! ```ignore //! table inet example-filter-ethernet { //! chain chain-for-outgoing-packets { //! type filter hook output priority 3; policy accept; //! ether daddr 00:00:00:00:00:00 drop //! counter packets 0 bytes 0 meta random > 2147483647 counter packets 0 bytes 0 //! } //! } //! ``` //! //! //! Everything created by this example can be removed by running //! ```bash //! # nft delete table inet example-filter-ethernet //! ``` use rustables::{nft_expr, sys::libc, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table}; use std::{ffi::CString, io, rc::Rc}; const TABLE_NAME: &str = "example-filter-ethernet"; const OUT_CHAIN_NAME: &str = "chain-for-outgoing-packets"; const BLOCK_THIS_MAC: &[u8] = &[0, 0, 0, 0, 0, 0]; fn main() -> Result<(), Error> { // For verbose explanations of what all these lines up until the rule creation does, see the // `add-rules` example. let mut batch = Batch::new(); let table = Rc::new(Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet)); batch.add(&Rc::clone(&table), rustables::MsgType::Add); let mut out_chain = Chain::new(&CString::new(OUT_CHAIN_NAME).unwrap(), Rc::clone(&table)); out_chain.set_hook(rustables::Hook::Out, 3); out_chain.set_policy(rustables::Policy::Accept); let out_chain = Rc::new(out_chain); batch.add(&Rc::clone(&out_chain), rustables::MsgType::Add); // === ADD RULE DROPPING ALL TRAFFIC TO THE MAC ADDRESS IN `BLOCK_THIS_MAC` === let mut block_ethernet_rule = Rule::new(Rc::clone(&out_chain)); // Check that the interface type is an ethernet interface. Must be done before we can check // payload values in the ethernet header. block_ethernet_rule.add_expr(&nft_expr!(meta iiftype)); block_ethernet_rule.add_expr(&nft_expr!(cmp == libc::ARPHRD_ETHER)); // Compare the ethernet destination address against the MAC address we want to drop block_ethernet_rule.add_expr(&nft_expr!(payload ethernet daddr)); block_ethernet_rule.add_expr(&nft_expr!(cmp == BLOCK_THIS_MAC)); // Drop the matching packets. block_ethernet_rule.add_expr(&nft_expr!(verdict drop)); batch.add(&block_ethernet_rule, rustables::MsgType::Add); // === FOR FUN, ADD A PACKET THAT MATCHES 50% OF ALL PACKETS === // This packet has a counter before and after the check that has 50% chance of matching. // So after a number of packets has passed through this rule, the first counter should have a // value approximately double that of the second counter. This rule has no verdict, so it never // does anything with the matching packets. let mut random_rule = Rule::new(Rc::clone(&out_chain)); // This counter expression will be evaluated (and increment the counter) for all packets coming // through. random_rule.add_expr(&nft_expr!(counter)); // Load a pseudo-random 32 bit unsigned integer into the netfilter register. random_rule.add_expr(&nft_expr!(meta random)); // Check if the random integer is larger than `u32::MAX/2`, thus having 50% chance of success. random_rule.add_expr(&nft_expr!(cmp > (::std::u32::MAX / 2).to_be())); // Add a second counter. This will only be incremented for the packets passing the random check. random_rule.add_expr(&nft_expr!(counter)); batch.add(&random_rule, rustables::MsgType::Add); // === FINALIZE THE TRANSACTION AND SEND THE DATA TO NETFILTER === match batch.finalize() { Some(mut finalized_batch) => { send_and_process(&mut finalized_batch)?; Ok(()) }, None => todo!() } } fn send_and_process(batch: &mut FinalizedBatch) -> Result<(), Error> { // Create a netlink socket to netfilter. let socket = mnl::Socket::new(mnl::Bus::Netfilter)?; // Send all the bytes in the batch. socket.send_all(&mut *batch)?; // Try to parse the messages coming back from netfilter. This part is still very unclear. let portid = socket.portid(); let mut buffer = vec![0; rustables::nft_nlmsg_maxsize() as usize]; let very_unclear_what_this_is_for = 2; while let Some(message) = socket_recv(&socket, &mut buffer[..])? { match mnl::cb_run(message, very_unclear_what_this_is_for, portid)? { mnl::CbResult::Stop => { break; } mnl::CbResult::Ok => (), } } Ok(()) } fn socket_recv<'a>(socket: &mnl::Socket, buf: &'a mut [u8]) -> Result, Error> { let ret = socket.recv(buf)?; if ret > 0 { Ok(Some(&buf[..ret])) } else { Ok(None) } } #[derive(Debug)] struct Error(String); impl From for Error { fn from(error: io::Error) -> Self { Error(error.to_string()) } }