aboutsummaryrefslogtreecommitdiff
path: root/rustables/examples/filter-ethernet.rs
blob: b16c49ed20e8665d75f89260c20c67800a420b4d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! 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<Option<&'a [u8]>, Error> {
    let ret = socket.recv(buf)?;
    if ret > 0 {
        Ok(Some(&buf[..ret]))
    } else {
        Ok(None)
    }
}

#[derive(Debug)]
struct Error(String);

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Error(error.to_string())
    }
}