aboutsummaryrefslogtreecommitdiff
path: root/rustables/examples/filter-ethernet.rs
blob: 6cebfc40acb380402e6377d1a89203e922ae716d (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
//! 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 nftnl::{nft_expr, nftnl_sys::libc, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table};
use std::{ffi::CString, io};

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 = Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet);
    batch.add(&table, nftnl::MsgType::Add);

    let mut out_chain = Chain::new(&CString::new(OUT_CHAIN_NAME).unwrap(), &table);
    out_chain.set_hook(nftnl::Hook::Out, 3);
    out_chain.set_policy(nftnl::Policy::Accept);
    batch.add(&out_chain, nftnl::MsgType::Add);

    // === ADD RULE DROPPING ALL TRAFFIC TO THE MAC ADDRESS IN `BLOCK_THIS_MAC` ===

    let mut block_ethernet_rule = Rule::new(&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, nftnl::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(&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, nftnl::MsgType::Add);

    // === FINALIZE THE TRANSACTION AND SEND THE DATA TO NETFILTER ===

    let finalized_batch = batch.finalize();
    send_and_process(&finalized_batch)?;
    Ok(())
}

fn send_and_process(batch: &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(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; nftnl::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())
    }
}