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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Copyright Pit Kleyersburg <pitkley@googlemail.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified or distributed
// except according to those terms.

//! This module implements the nftables backend for DFW.

use crate::{errors::*, FirewallBackend, ProcessContext};
use slog::{debug, info, o, trace};
use std::{
    io::{prelude::*, BufWriter},
    process::Command,
};
use strum::Display;

mod process;
mod rule;
pub mod types;

const NF_IP_PRI_NAT_DST: i16 = -100;
const NF_IP_PRI_FILTER: i16 = 0;
const NF_IP_PRI_NAT_SRC: i16 = 100;

const NF_PRIORITY_IP_NAT_PREROUTING_DFW: i16 = NF_IP_PRI_NAT_DST - 5;
const NF_PRIORITY_IP6_NAT_PREROUTING_DFW: i16 = NF_IP_PRI_NAT_DST - 5;
const NF_PRIORITY_INET_FILTER_ANY_DFW: i16 = NF_IP_PRI_FILTER - 5;
const NF_PRIORITY_IP_NAT_POSTROUTING_DFW: i16 = NF_IP_PRI_NAT_SRC - 5;
const NF_PRIORITY_IP6_NAT_POSTROUTING_DFW: i16 = NF_IP_PRI_NAT_SRC - 5;

const DFW_MARK: &str = "0xdf";

/// Marker struct to implement nftables as a firewall backend.
#[derive(Debug)]
pub struct Nftables;
impl FirewallBackend for Nftables {
    type Rule = String;
    type Defaults = types::Defaults;

    fn apply(rules: Vec<Self::Rule>, ctx: &ProcessContext<Nftables>) -> Result<()> {
        if ctx.dry_run {
            info!(ctx.logger, "Performing dry-run, will not update any rules");
        } else {
            // To atomically update the ruleset, we need to write a file and pass that to `nft -f`.
            let rule_file = tempfile::Builder::new().tempfile()?;
            let rule_file_path = rule_file.as_ref().as_os_str().to_os_string();
            debug!(ctx.logger, "Writing rules to temporary file";
                   o!("file_path" => rule_file_path.to_string_lossy().into_owned()));
            let mut writer = BufWriter::new(rule_file);

            for rule in rules {
                writeln!(writer, "{}", rule)?;
            }
            writer.flush()?;
            trace!(ctx.logger, "Finished writing rules to temporary file");

            info!(ctx.logger, "Applying rules (using nft)");
            let output = Command::new("nft").arg("-f").arg(rule_file_path).output()?;
            if !output.status.success() {
                return Err(DFWError::NFTablesError {
                    stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
                    stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
                }
                .into());
            } else {
                return Ok(());
            }
        }

        Ok(())
    }
}

/// Representation of nftables table-families.
#[derive(Debug, Clone, Copy, Display)]
#[strum(serialize_all = "snake_case")]
pub enum Family {
    /// IPv4 table family
    Ip,
    /// IPv6 table family
    Ip6,
    /// Dualstack IPv4/IPv6 table family
    Inet,
    /// ARP table family
    Arp,
    /// Bridge table family
    Bridge,
    /// Netdev table family
    Netdev,
}

/// Representation of nftables chain-types.
///
/// ## Attribution
///
/// Parts of the documentation have been taken from
/// <https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains>.
#[derive(Debug, Clone, Copy, Display)]
#[strum(serialize_all = "snake_case")]
pub enum Type {
    /// Is used to filter packets.
    ///
    /// Supported by the following table-families:
    ///
    /// * Arp
    /// * Bridge
    /// * Ip
    /// * Ip6
    /// * Inet
    Filter,
    /// Is used to reroute packets if any relevant IP header field or the packet mark is modified.
    ///
    /// Supported by the following table-families:
    ///
    /// * Ip
    /// * Ip6
    Route,
    /// Is used to perform Networking Address Translation (NAT).
    ///
    /// Supported by the following table-families:
    ///
    /// * Ip
    /// * Ip6
    Nat,
}

/// Representation of nftables chain hooks.
///
/// Order of hook execution:
///
/// * Ingress _(for netdev-family)_
/// * Prerouting
/// * Input
/// * Forward
/// * Output
/// * Postrouting
///
/// ## Attribution
///
/// Parts of the documentation have been taken from
/// <https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains>.
#[derive(Debug, Clone, Copy, Display)]
#[strum(serialize_all = "snake_case")]
pub enum Hook {
    /// Ingress allows traffic-filtering before pre-routing, after the packet traversed the NIC.
    ///
    /// Only available for the netdev-family.
    Ingress,
    /// Prerouting allows traffic-filtering before the packets have been routed.
    Prerouting,
    /// Input allows traffic-filtering for packets that have been routed to the local system.
    Input,
    /// Forward allows traffic-filtering for packets that were not routed to the local system.
    Forward,
    /// Output allows traffic-filtering for packets leaving the local system.
    Output,
    /// Postrouting allows traffic-filtering for already routed packets leaving the local system.
    Postrouting,
}