nftables_async/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use futures_util::AsyncWriteExt;
4use nftables::{helper::NftablesError, schema::Nftables};
5use process::Process;
6
7/// The default "nft" program used via PATH lookup.
8pub const NFT_DEFAULT_PROGRAM: &str = "nft";
9
10pub mod process;
11
12/// Apply the given [Nftables] ruleset, optionally overriding which "nft" binary to use and adding extra arguments.
13pub async fn apply_ruleset<P: Process>(
14    nftables: Nftables<'_>,
15    program: Option<&str>,
16    args: Option<Vec<&str>>,
17) -> Result<(), NftablesError> {
18    let payload = serde_json::to_string(&nftables).map_err(NftablesError::NftInvalidJson)?;
19    apply_ruleset_raw::<P>(payload, program, args).await
20}
21
22/// Apply the given ruleset as a [String] payload instead of an [Nftables] reference.
23pub async fn apply_ruleset_raw<P: Process>(
24    payload: String,
25    program: Option<&str>,
26    args: Option<Vec<&str>>,
27) -> Result<(), NftablesError> {
28    let program = program.unwrap_or(NFT_DEFAULT_PROGRAM);
29    let mut arg_vec = vec!["-j", "-f", "-"];
30
31    if let Some(args) = args {
32        arg_vec.extend(args);
33    }
34
35    let mut child =
36        P::spawn(program, arg_vec, true).map_err(|err| NftablesError::NftExecution {
37            program: program.to_owned().into(),
38            inner: err,
39        })?;
40
41    let mut stdin = child
42        .take_stdin()
43        .expect("Stdin was piped to the process but could not be retrieved");
44    stdin
45        .write_all(payload.as_bytes())
46        .await
47        .map_err(|err| NftablesError::NftExecution {
48            program: program.to_owned().into(),
49            inner: err,
50        })?;
51    drop(stdin);
52
53    match child.wait_with_output().await {
54        Ok(output) if output.status.success() => Ok(()),
55        Ok(output) => {
56            let stdout = read(program, output.stdout)?;
57            let stderr = read(program, output.stderr)?;
58
59            Err(NftablesError::NftFailed {
60                program: program.to_owned().into(),
61                hint: "applying ruleset".to_string(),
62                stdout,
63                stderr,
64            })
65        }
66        Err(err) => Err(NftablesError::NftExecution {
67            program: program.to_owned().into(),
68            inner: err,
69        }),
70    }
71}
72
73/// Get the current ruleset as [Nftables] via, optionally overriding which "nft" binary to use and what extra arguments to pass.
74pub async fn get_current_ruleset<P: Process>(
75    program: Option<&str>,
76    args: Option<Vec<&str>>,
77) -> Result<Nftables<'static>, NftablesError> {
78    let output = get_current_ruleset_raw::<P>(program, args).await?;
79    serde_json::from_str(&output).map_err(NftablesError::NftInvalidJson)
80}
81
82/// Get the current ruleset as a [String] payload instead of an [Nftables] instance.
83pub async fn get_current_ruleset_raw<P: Process>(
84    program: Option<&str>,
85    args: Option<Vec<&str>>,
86) -> Result<String, NftablesError> {
87    let program = program.unwrap_or(NFT_DEFAULT_PROGRAM);
88    let mut arg_vec = vec!["-j", "list", "ruleset"];
89    if let Some(args) = args {
90        arg_vec.extend(args);
91    }
92
93    let output = P::output(program, arg_vec)
94        .await
95        .map_err(|err| NftablesError::NftExecution {
96            program: program.to_owned().into(),
97            inner: err,
98        })?;
99
100    let stdout = read(program, output.stdout)?;
101
102    if !output.status.success() {
103        let stderr = read(program, output.stderr)?;
104
105        return Err(NftablesError::NftFailed {
106            program: program.to_owned().into(),
107            hint: "getting the current ruleset".to_string(),
108            stdout,
109            stderr,
110        });
111    }
112
113    Ok(stdout)
114}
115
116#[inline]
117fn read(program: &str, stream: Vec<u8>) -> Result<String, NftablesError> {
118    String::from_utf8(stream).map_err(|err| NftablesError::NftOutputEncoding {
119        program: program.to_owned().into(),
120        inner: err,
121    })
122}