Skip to content

Commit

Permalink
Merge pull request #67 from namib-project/flow-stmt
Browse files Browse the repository at this point in the history
Implement Flow statement
  • Loading branch information
jwhb authored Oct 21, 2024
2 parents 3efe25f + fd88573 commit ae6d307
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 11 deletions.
68 changes: 68 additions & 0 deletions resources/test/json/flow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"nftables": [
{
"metainfo": {
"version": "1.0.9",
"release_name": "Old Doc Yak #3",
"json_schema_version": 1
}
},
{
"table": {
"family": "inet",
"name": "named_counter_demo",
"handle": 3
}
},
{
"flowtable": {
"family": "inet",
"name": "flowed",
"table": "named_counter_demo",
"handle": 2,
"hook": "ingress",
"prio": 0,
"dev": "lo"
}
},
{
"chain": {
"family": "inet",
"table": "named_counter_demo",
"name": "forward",
"handle": 1,
"type": "filter",
"hook": "forward",
"prio": 0,
"policy": "accept"
}
},
{
"rule": {
"family": "inet",
"table": "named_counter_demo",
"chain": "forward",
"handle": 3,
"expr": [
{
"match": {
"op": "in",
"left": {
"ct": {
"key": "state"
}
},
"right": "established"
}
},
{
"flow": {
"op": "add",
"flowtable": "@flowed"
}
}
]
}
}
]
}
15 changes: 15 additions & 0 deletions resources/test/nft/flow.nft
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/sbin/nft -f

flush ruleset

table inet named_counter_demo {
flowtable flowed {
hook ingress priority filter
devices = { lo }
}

chain forward {
type filter hook forward priority filter; policy accept;
ct state established flow add @flowed
}
}
22 changes: 18 additions & 4 deletions src/schema.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashSet;

use crate::{expr::Expression, stmt::Statement, types::*};
use crate::{expr::Expression, stmt::Statement, types::*, visitor::single_string_to_option_vec};

use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -298,16 +298,30 @@ pub struct Element {
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
/// Flowtables allow you to accelerate packet forwarding in software (and in hardware if your NIC supports it)
/// by using a conntrack-based network stack bypass.
pub struct FlowTable {
pub family: String,
/// Family the FlowTable is addressed by.
pub family: NfFamily,
/// Table the FlowTable is addressed by.
pub table: String,
/// Name the FlowTable is addressed by.
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// Handle of the FlowTable object in the current ruleset.
pub handle: Option<u32>,
/// Hook the FlowTable resides in.
pub hook: Option<NfHook>,
#[serde(skip_serializing_if = "Option::is_none")]
/// The *priority* can be a signed integer or *filter* which stands for 0.
/// Addition and subtraction can be used to set relative priority, e.g., filter + 5 is equal to 5.
pub prio: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "single_string_to_option_vec"
)]
/// The *devices* are specified as iifname(s) of the input interface(s) of the traffic that should be offloaded.
/// Devices are required for both traffic directions.
pub dev: Option<Vec<String>>,
}

Expand Down
12 changes: 12 additions & 0 deletions src/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub enum Statement {
QuotaRef(String),
Limit(Limit),

/// The Flow statement offloads matching network traffic to flowtables,
/// enabling faster forwarding by bypassing standard processing.
Flow(Flow),
FWD(Option<FWD>),
/// Disable connection tracking for the packet.
Notrack,
Expand Down Expand Up @@ -185,6 +188,15 @@ pub struct Limit {
pub inv: Option<bool>,
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
/// Forward a packet to a different destination.
pub struct Flow {
/// Operator on flow/set.
pub op: SetOp,
/// The [flow table][crate::schema::FlowTable]'s name.
pub flowtable: String,
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
/// Forward a packet to a different destination.
pub struct FWD {
Expand Down
108 changes: 101 additions & 7 deletions tests/json_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use nftables::expr::{Expression, Meta, MetaKey, NamedExpression};
use nftables::stmt::{Counter, Match, Operator, Queue, Statement};
use nftables::expr::{self, Expression, Meta, MetaKey, NamedExpression};
use nftables::stmt::{self, Counter, Match, Operator, Queue, Statement};
use nftables::{schema::*, types::*};
use serde_json::json;
use std::fs::{self, File};
Expand All @@ -24,8 +24,12 @@ fn test_deserialize_json_files() {

#[test]
fn test_chain_table_rule_inet() {
// nft add table inet some_inet_table
// nft add chain inet some_inet_table some_inet_chain '{ type filter hook forward priority 0; policy accept; }'
// Equivalent nft command:
// ```
// nft "add table inet some_inet_table;
// add chain inet some_inet_table some_inet_chain
// '{ type filter hook forward priority 0; policy accept; }'"
// ```
let expected: Nftables = Nftables {
objects: vec![
NfObject::CmdObject(NfCmd::Add(NfListObject::Table(Table {
Expand All @@ -47,15 +51,100 @@ fn test_chain_table_rule_inet() {
}))),
],
};
let json = json!({"nftables":[{"add":{"table":{"family":"inet","name":"some_inet_table"}}},{"add":{"chain":{"family":"inet","table":"some_inet_table","name":"some_inet_chain","type":"filter","hook":"forward","policy":"accept"}}}]});
let json = json!({"nftables":[
{"add":{"table":{"family":"inet","name":"some_inet_table"}}},
{"add":{"chain":{"family":"inet","table":"some_inet_table",
"name":"some_inet_chain","type":"filter","hook":"forward","policy":"accept"}}}
]});
println!("{}", &json);
let parsed: Nftables = serde_json::from_value(json).unwrap();
assert_eq!(expected, parsed);
}

#[test]
/// Test JSON serialization of flow and flowtable.
fn test_flowtable() {
// equivalent nft command:
// ```
// nft 'flush ruleset; add table inet some_inet_table;
// add chain inet some_inet_table forward;
// add flowtable inet some_inet_table flowed { hook ingress priority filter; devices = { lo }; };
// add rule inet some_inet_table forward ct state established flow add @flowed'
// ```
let expected: Nftables = Nftables {
objects: vec![
NfObject::ListObject(Box::new(NfListObject::Table(Table {
family: NfFamily::INet,
name: "some_inet_table".to_string(),
handle: None,
}))),
NfObject::ListObject(Box::new(NfListObject::FlowTable(FlowTable {
family: NfFamily::INet,
table: "some_inet_table".to_string(),
name: "flowed".to_string(),
handle: None,
hook: Some(NfHook::Ingress),
prio: Some(0),
dev: Some(vec!["lo".to_string()]),
}))),
NfObject::ListObject(Box::new(NfListObject::Chain(Chain {
family: NfFamily::INet,
table: "some_inet_table".to_string(),
name: "some_inet_chain".to_string(),
newname: None,
handle: None,
_type: Some(NfChainType::Filter),
hook: Some(NfHook::Forward),
prio: None,
dev: None,
policy: Some(NfChainPolicy::Accept),
}))),
NfObject::ListObject(Box::new(NfListObject::Rule(Rule {
family: NfFamily::INet,
table: "some_inet_table".to_string(),
chain: "some_inet_chain".to_string(),
expr: vec![
Statement::Flow(stmt::Flow {
op: stmt::SetOp::Add,
flowtable: "@flowed".to_string(),
}),
Statement::Match(Match {
left: Expression::Named(NamedExpression::CT(expr::CT {
key: "state".to_string(),
family: None,
dir: None,
})),
op: Operator::IN,
right: Expression::String("established".to_string()),
}),
],
handle: None,
index: None,
comment: None,
}))),
],
};
let json = json!({"nftables":[
{"table":{"family":"inet","name":"some_inet_table"}},
{"flowtable":{"family":"inet","table":"some_inet_table","name":"flowed",
"hook":"ingress","prio":0,"dev":["lo"]}},
{"chain":{"family":"inet","table":"some_inet_table","name":"some_inet_chain",
"type":"filter","hook":"forward","policy":"accept"}},
{"rule":{"family":"inet","table":"some_inet_table","chain":"some_inet_chain",
"expr":[{"flow":{"op":"add","flowtable":"@flowed"}},
{"match":{"left":{"ct":{"key":"state"}},"right":"established","op":"in"}}]}}]});
println!("{}", &json);
let parsed: Nftables = serde_json::from_value(json).unwrap();
assert_eq!(expected, parsed);
}

#[test]
fn test_insert() {
// nft insert rule inet some_inet_table some_inet_chain position 0 iifname "br-lan" oifname "wg_exit" counter accept
// Equivalent nft command:
// ```
// nft 'insert rule inet some_inet_table some_inet_chain position 0
// iifname "br-lan" oifname "wg_exit" counter accept'
// ```
let expected: Nftables = Nftables {
objects: vec![NfObject::CmdObject(NfCmd::Insert(NfListObject::Rule(
Rule {
Expand Down Expand Up @@ -86,7 +175,12 @@ fn test_insert() {
},
)))],
};
let json = json!({"nftables":[{"insert":{"rule":{"family":"inet","table":"some_inet_table","chain":"some_inet_chain","expr":[{"match":{"left":{"meta":{"key":"iifname"}},"right":"br-lan","op":"=="}},{"match":{"left":{"meta":{"key":"oifname"}},"right":"wg_exit","op":"=="}},{"counter":null},{"accept":null}],"index":0,"comment":null}}}]});
let json = json!({"nftables":[{"insert":
{"rule":{"family":"inet","table":"some_inet_table","chain":"some_inet_chain","expr":[
{"match":{"left":{"meta":{"key":"iifname"}},"right":"br-lan","op":"=="}},
{"match":{"left":{"meta":{"key":"oifname"}},"right":"wg_exit","op":"=="}},
{"counter":null},{"accept":null}
],"index":0,"comment":null}}}]});
println!("{}", &json);
let parsed: Nftables = serde_json::from_value(json).unwrap();
assert_eq!(expected, parsed);
Expand Down

0 comments on commit ae6d307

Please sign in to comment.