nftables: commands: add types

Add rust types for most of the nftables commands as defined by
libnftables-json [1].

Different commands require different keys to be set for the same type
of object. E.g. deleting an object usually only requires a name +
name of the container (table/chain/rule). Creating an object usually
requires a few more keys, depending on the type of object created.

In order to be able to model the different objects for the different
commands, I've created specific models for a command where necessary.
Parts that are common across multiple commands (e.g. names) have been
moved to their own structs, so they can be reused.

[1] https://manpages.debian.org/bookworm/libnftables1/libnftables-json.5.en.html#COMMAND_OBJECTS

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
This commit is contained in:
Stefan Hanreich 2024-04-17 14:55:35 +02:00 committed by Thomas Lamprecht
parent 613c4b1424
commit 6b40860fd5
3 changed files with 1004 additions and 1 deletions

View File

@ -0,0 +1,233 @@
use std::ops::{Deref, DerefMut};
use crate::helper::Null;
use crate::types::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Commands {
nftables: Vec<Command>,
}
impl Commands {
pub fn new(commands: Vec<Command>) -> Self {
Self { nftables: commands }
}
}
impl Deref for Commands {
type Target = Vec<Command>;
fn deref(&self) -> &Self::Target {
&self.nftables
}
}
impl DerefMut for Commands {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.nftables
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Command {
Add(Add),
Create(Add),
Delete(Delete),
Flush(Flush),
List(List),
// Insert(super::Rule),
// Rename(RenameChain),
// Replace(super::Rule),
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum List {
Chains(Null),
Sets(Null),
}
impl List {
#[inline]
pub fn chains() -> Command {
Command::List(List::Chains(Null))
}
#[inline]
pub fn sets() -> Command {
Command::List(List::Sets(Null))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Add {
Table(AddTable),
Chain(AddChain),
Rule(AddRule),
Set(AddSet),
Map(AddMap),
Limit(AddLimit),
Element(AddElement),
#[serde(rename = "ct helper")]
CtHelper(AddCtHelper),
}
impl Add {
#[inline]
pub fn table(table: impl Into<AddTable>) -> Command {
Command::Add(Add::Table(table.into()))
}
#[inline]
pub fn chain(chain: impl Into<AddChain>) -> Command {
Command::Add(Add::Chain(chain.into()))
}
#[inline]
pub fn rule(rule: impl Into<AddRule>) -> Command {
Command::Add(Add::Rule(rule.into()))
}
#[inline]
pub fn set(set: impl Into<AddSet>) -> Command {
Command::Add(Add::Set(set.into()))
}
#[inline]
pub fn map(map: impl Into<AddMap>) -> Command {
Command::Add(Add::Map(map.into()))
}
#[inline]
pub fn limit(limit: impl Into<AddLimit>) -> Command {
Command::Add(Add::Limit(limit.into()))
}
#[inline]
pub fn element(element: impl Into<AddElement>) -> Command {
Command::Add(Add::Element(element.into()))
}
#[inline]
pub fn ct_helper(ct_helper: impl Into<AddCtHelper>) -> Command {
Command::Add(Add::CtHelper(ct_helper.into()))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Flush {
Table(TableName),
Chain(ChainName),
Set(SetName),
Map(SetName),
Ruleset(Null),
}
impl Flush {
#[inline]
pub fn table(table: impl Into<TableName>) -> Command {
Command::Flush(Flush::Table(table.into()))
}
#[inline]
pub fn chain(chain: impl Into<ChainName>) -> Command {
Command::Flush(Flush::Chain(chain.into()))
}
#[inline]
pub fn set(set: impl Into<SetName>) -> Command {
Command::Flush(Flush::Set(set.into()))
}
#[inline]
pub fn map(map: impl Into<SetName>) -> Command {
Command::Flush(Flush::Map(map.into()))
}
#[inline]
pub fn ruleset() -> Command {
Command::Flush(Flush::Ruleset(Null))
}
}
impl From<TableName> for Flush {
#[inline]
fn from(value: TableName) -> Self {
Flush::Table(value)
}
}
impl From<ChainName> for Flush {
#[inline]
fn from(value: ChainName) -> Self {
Flush::Chain(value)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Delete {
Table(TableName),
Chain(ChainName),
Set(SetName),
}
impl Delete {
#[inline]
pub fn table(table: impl Into<TableName>) -> Command {
Command::Delete(Delete::Table(table.into()))
}
#[inline]
pub fn chain(chain: impl Into<ChainName>) -> Command {
Command::Delete(Delete::Chain(chain.into()))
}
#[inline]
pub fn set(set: impl Into<SetName>) -> Command {
Command::Delete(Delete::Set(set.into()))
}
}
impl From<TableName> for Delete {
#[inline]
fn from(value: TableName) -> Self {
Delete::Table(value)
}
}
impl From<ChainName> for Delete {
#[inline]
fn from(value: ChainName) -> Self {
Delete::Chain(value)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ListOutput {
Metainfo(serde_json::Value),
// Table(super::AddTable),
Chain(ListChain),
// Rule(super::Rule),
Set(ListSet),
// Map(super::Map),
// Element(super::SetElement),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CommandOutput {
pub nftables: Vec<ListOutput>,
}
impl Deref for CommandOutput {
type Target = Vec<ListOutput>;
fn deref(&self) -> &Self::Target {
&self.nftables
}
}

View File

@ -1,7 +1,9 @@
pub mod command;
pub mod expression;
pub mod helper;
pub mod statement;
pub mod types;
pub use command::Command;
pub use expression::Expression;
pub use statement::Statement;

View File

@ -1,8 +1,92 @@
use std::fmt::Display;
use std::ops::{Deref, DerefMut};
use crate::expression::IpFamily;
use crate::helper::{NfVec, Null};
use crate::{Expression, Statement};
use serde::{Deserialize, Serialize};
use crate::helper::Null;
#[cfg(feature = "config-ext")]
use proxmox_ve_config::guest::types::Vmid;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Handle(i32);
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TableFamily {
Ip,
Ip6,
Inet,
Arp,
Bridge,
Netdev,
}
serde_plain::derive_display_from_serialize!(TableFamily);
impl TableFamily {
pub fn ip_families(&self) -> Vec<IpFamily> {
match self {
TableFamily::Ip => vec![IpFamily::Ip],
TableFamily::Ip6 => vec![IpFamily::Ip6],
_ => vec![IpFamily::Ip, IpFamily::Ip6],
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ElementType {
Ifname,
Ipv4Addr,
Ipv6Addr,
}
serde_plain::derive_display_from_serialize!(ElementType);
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ChainType {
Filter,
Nat,
Route,
}
serde_plain::derive_display_from_serialize!(ChainType);
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum SetPolicy {
Performance,
Memory,
}
serde_plain::derive_display_from_serialize!(SetPolicy);
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum SetFlag {
Constant,
Interval,
Timeout,
}
serde_plain::derive_display_from_serialize!(SetFlag);
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum OutputType {
Verdict,
Type(ElementType),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Hook {
Prerouting,
Input,
Forward,
Output,
Postrouting,
}
serde_plain::derive_display_from_serialize!(Hook);
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
@ -30,6 +114,32 @@ impl Display for Verdict {
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ChainPolicy {
Accept,
Drop,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum PriorityKeyword {
Raw,
Mangle,
DstNat,
Filter,
Security,
SrcNat,
Out,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Priority {
Keyword(PriorityKeyword),
Number(i64),
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum RateUnit {
Packets,
@ -47,6 +157,529 @@ pub enum RateTimescale {
Day,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TableName {
family: TableFamily,
name: String,
}
impl TableName {
pub fn new(family: TableFamily, name: impl Into<String>) -> Self {
Self {
family,
name: name.into(),
}
}
pub fn family(&self) -> &TableFamily {
&self.family
}
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TablePart {
family: TableFamily,
table: String,
}
impl TablePart {
pub fn new(family: TableFamily, name: impl Into<String>) -> Self {
Self {
family,
table: name.into(),
}
}
pub fn family(&self) -> &TableFamily {
&self.family
}
pub fn table(&self) -> &str {
&self.table
}
}
impl From<TablePart> for TableName {
fn from(t: TablePart) -> Self {
Self {
family: t.family,
name: t.table,
}
}
}
impl From<TableName> for TablePart {
fn from(t: TableName) -> Self {
Self {
family: t.family,
table: t.name,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ChainName {
#[serde(flatten)]
table: TablePart,
name: String,
}
impl From<AddChain> for ChainName {
fn from(value: AddChain) -> Self {
Self {
table: value.table,
name: value.name,
}
}
}
impl From<ListChain> for ChainName {
fn from(value: ListChain) -> Self {
Self {
table: value.table,
name: value.name,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ChainPart {
#[serde(flatten)]
table: TablePart,
chain: String,
}
impl ChainPart {
pub fn new(table: TablePart, chain: impl Into<String>) -> Self {
Self {
table,
chain: chain.into(),
}
}
pub fn table(&self) -> &TablePart {
&self.table
}
pub fn name(&self) -> &str {
&self.chain
}
}
impl From<ChainName> for ChainPart {
fn from(c: ChainName) -> Self {
Self {
table: c.table,
chain: c.name,
}
}
}
impl From<ChainPart> for ChainName {
fn from(c: ChainPart) -> Self {
Self {
table: c.table,
name: c.chain,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AddTable {
family: TableFamily,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
handle: Option<Handle>,
}
impl AddTable {
pub fn new(family: TableFamily, name: impl Into<String>) -> Self {
Self {
family,
name: name.into(),
handle: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BaseChainConfig {
#[serde(rename = "type")]
ty: ChainType,
hook: Hook,
prio: Expression,
policy: ChainPolicy,
/// netdev family only
#[serde(skip_serializing_if = "Option::is_none")]
dev: Option<String>,
}
impl BaseChainConfig {
pub fn new(
ty: ChainType,
hook: Hook,
prio: impl Into<Expression>,
policy: ChainPolicy,
) -> Self {
Self {
ty,
hook,
prio: prio.into(),
policy,
dev: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AddChain {
#[serde(flatten)]
table: TablePart,
name: String,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
config: Option<BaseChainConfig>,
}
impl AddChain {
pub fn new(table: TablePart, name: impl Into<String>) -> Self {
Self {
table,
name: name.into(),
config: None,
}
}
pub fn new_base_chain(
table: TablePart,
name: impl Into<String>,
config: BaseChainConfig,
) -> Self {
Self {
table,
name: name.into(),
config: Some(config),
}
}
}
impl From<ChainPart> for AddChain {
#[inline]
fn from(part: ChainPart) -> Self {
Self::new(part.table, part.chain)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AddRule {
#[serde(flatten)]
chain: ChainPart,
#[serde(skip_serializing_if = "Option::is_none")]
handle: Option<Handle>,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
comment: Option<String>,
expr: Vec<Statement>,
}
impl Deref for AddRule {
type Target = Vec<Statement>;
fn deref(&self) -> &Self::Target {
&self.expr
}
}
impl DerefMut for AddRule {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.expr
}
}
impl AddRule {
pub fn from_statement(chain: ChainPart, expression: impl Into<Statement>) -> Self {
Self {
chain,
expr: vec![expression.into()],
handle: None,
index: None,
comment: None,
}
}
pub fn from_statements<I: IntoIterator<Item = Statement>>(
chain: ChainPart,
expression: I,
) -> Self {
Self {
chain,
expr: expression.into_iter().collect(),
handle: None,
index: None,
comment: None,
}
}
pub fn new(chain: ChainPart) -> Self {
Self {
chain,
expr: Vec::new(),
handle: None,
index: None,
comment: None,
}
}
pub fn with_comment(mut self, comment: impl Into<String>) -> Self {
self.comment = Some(comment.into());
self
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SetConfig {
#[serde(flatten)]
name: SetName,
#[serde(rename = "type", default, skip_serializing_if = "Vec::is_empty")]
ty: NfVec<ElementType>,
#[serde(skip_serializing_if = "Option::is_none")]
policy: Option<SetPolicy>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
flags: Vec<SetFlag>,
#[serde(skip_serializing_if = "Option::is_none")]
timeout: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
gc_interval: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
size: Option<i64>,
}
impl SetConfig {
pub fn new(name: impl Into<SetName>, ty: impl IntoIterator<Item = ElementType>) -> Self {
Self {
name: name.into(),
ty: NfVec::from_iter(ty),
flags: Vec::new(),
policy: None,
timeout: None,
gc_interval: None,
size: None,
}
}
pub fn name(&self) -> &SetName {
&self.name
}
pub fn with_flag(mut self, flag: SetFlag) -> Self {
self.flags.push(flag);
self
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct AddMap {
#[serde(flatten)]
config: SetConfig,
map: OutputType,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
elem: NfVec<MapElem>,
}
impl AddMap {
pub fn new(config: SetConfig, output_type: OutputType) -> Self {
Self {
config,
map: output_type,
elem: NfVec::new(),
}
}
}
impl Deref for AddMap {
type Target = Vec<MapElem>;
fn deref(&self) -> &Self::Target {
&self.elem
}
}
impl DerefMut for AddMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.elem
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AddSet {
#[serde(flatten)]
config: SetConfig,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
elem: NfVec<SetElem>,
}
impl From<SetConfig> for AddSet {
fn from(value: SetConfig) -> Self {
Self {
config: value,
elem: NfVec::new(),
}
}
}
impl Deref for AddSet {
type Target = Vec<SetElem>;
fn deref(&self) -> &Self::Target {
&self.elem
}
}
impl DerefMut for AddSet {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.elem
}
}
impl AddSet {
pub fn new(config: impl Into<SetConfig>, elements: impl IntoIterator<Item = SetElem>) -> Self {
Self {
config: config.into(),
elem: NfVec::from_iter(elements),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SetName {
#[serde(flatten)]
table: TablePart,
name: String,
}
impl SetName {
pub fn new(table: TablePart, name: impl Into<String>) -> Self {
Self {
table,
name: name.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SetElem(Expression);
impl From<Expression> for SetElem {
#[inline]
fn from(value: Expression) -> Self {
Self(value)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum MapValue {
Expression(Expression),
Verdict(Verdict),
// Concat
}
impl From<Verdict> for MapValue {
#[inline]
fn from(value: Verdict) -> Self {
Self::Verdict(value)
}
}
impl From<Expression> for MapValue {
#[inline]
fn from(value: Expression) -> Self {
Self::Expression(value)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MapElem((Expression, MapValue));
impl MapElem {
pub fn new(key: Expression, value: impl Into<MapValue>) -> Self {
Self((key, value.into()))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AddSetElement {
#[serde(flatten)]
set: SetName,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
elem: Vec<SetElement>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AddMapElement {
#[serde(flatten)]
map: SetName,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
elem: Vec<MapElement>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum AddElement {
Set(AddSetElement),
Map(AddMapElement),
}
impl AddElement {
pub fn map_from_expressions(
map: SetName,
elem: impl IntoIterator<Item = (Expression, MapValue)>,
) -> Self {
Self::Map(AddMapElement {
map,
elem: Vec::from_iter(
elem.into_iter()
.map(|(key, value)| MapElem::new(key, value).into()),
),
})
}
pub fn set_from_expressions(set: SetName, elem: impl IntoIterator<Item = Expression>) -> Self {
Self::Set(AddSetElement {
set,
elem: Vec::from_iter(elem.into_iter().map(SetElement::from)),
})
}
}
impl From<AddSetElement> for AddElement {
fn from(value: AddSetElement) -> Self {
AddElement::Set(value)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ElemConfig {
timeout: Option<i64>,
@ -67,3 +700,138 @@ impl ElemConfig {
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SetElemObject {
#[serde(flatten)]
config: ElemConfig,
elem: SetElem,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MapElemObject {
#[serde(flatten)]
config: ElemConfig,
elem: MapElem,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum MapElement {
#[serde(rename = "elem")]
Object(MapElemObject),
#[serde(untagged)]
Value(MapElem),
}
impl From<MapElem> for MapElement {
fn from(value: MapElem) -> Self {
Self::Value(value)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum SetElement {
#[serde(rename = "elem")]
Object(SetElemObject),
#[serde(untagged)]
Value(SetElem),
}
impl From<Expression> for SetElement {
#[inline]
fn from(value: Expression) -> Self {
Self::Value(SetElem::from(value))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub struct AddLimit {
#[serde(flatten)]
table: TablePart,
name: String,
rate: i64,
#[serde(skip_serializing_if = "Option::is_none")]
unit: Option<RateUnit>,
#[serde(skip_serializing_if = "Option::is_none")]
per: Option<RateTimescale>,
#[serde(skip_serializing_if = "Option::is_none")]
burst: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
inv: Option<bool>,
}
impl AddLimit {
pub fn new(table: TablePart, name: String, rate: i64) -> Self {
Self {
table,
name,
rate,
unit: None,
per: None,
burst: None,
inv: None,
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum L3Protocol {
Ip,
Ip6,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum CtHelperProtocol {
TCP,
UDP,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename = "ct helper")]
pub struct AddCtHelper {
#[serde(flatten)]
pub table: TablePart,
pub name: String,
#[serde(rename = "type")]
pub ty: String,
pub protocol: CtHelperProtocol,
pub l3proto: Option<L3Protocol>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ListChain {
#[serde(flatten)]
table: TablePart,
name: String,
handle: i64,
#[serde(flatten)]
config: Option<BaseChainConfig>,
}
impl ListChain {
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ListSet {
#[serde(flatten)]
name: SetName,
}
impl ListSet {
pub fn name(&self) -> &SetName {
&self.name
}
}