From 3c47e7566b48c9c51aeca5d50e78579f7b819553 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Wed, 22 Apr 2026 21:54:59 +0500 Subject: [PATCH 01/22] Refactor to consolidate APIs into `contract` package and introduce `NFT` interface for better modularity and maintainability. --- contract/command.go | 12 +++++++++ contract/nft.go | 31 ++++++++++++++++++++++ contract/nft/chain.go | 46 ++++++++++++++++++++++++++++++++ contract/nft/rule.go | 31 ++++++++++++++++++++++ contract/nft/table.go | 24 +++++++++++++++++ contract/nft/version.go | 8 ++++++ internal/chain/chain.go | 47 +++------------------------------ internal/command/command.go | 12 +++------ internal/rule/rule.go | 34 +++--------------------- internal/table/table.go | 28 +++----------------- nft.go | 52 ++++++++++--------------------------- version.go | 11 ++------ 12 files changed, 182 insertions(+), 154 deletions(-) create mode 100644 contract/command.go create mode 100644 contract/nft.go create mode 100644 contract/nft/chain.go create mode 100644 contract/nft/rule.go create mode 100644 contract/nft/table.go create mode 100644 contract/nft/version.go diff --git a/contract/command.go b/contract/command.go new file mode 100644 index 0000000..ec8764e --- /dev/null +++ b/contract/command.go @@ -0,0 +1,12 @@ +package contract + +// Run is a function that executes nft command. +type Run func(arg ...string) error + +type Command interface { + // Run nft command. + Run(arg ...string) error + + // RunWithOutput Run nft command with output. + RunWithOutput(arg ...string) (string, error) +} diff --git a/contract/nft.go b/contract/nft.go new file mode 100644 index 0000000..28303e6 --- /dev/null +++ b/contract/nft.go @@ -0,0 +1,31 @@ +package contract + +import "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" + +// NFT A client for working with nftables +type NFT interface { + // Command returns the command used to execute nft. + // You can execute your raw request. + Command() Command + + // Clear clears all rules. + // + // This command is equivalent to: + // nft flush ruleset + Clear() error + + // Version returns the version of nftables. + // + // This command is equivalent to: + // nft -V + Version() (nft.Version, error) + + // Table returns an API for working with tables. + Table() nft.Table + + // Chain returns an API for working with chains. + Chain() nft.Chain + + // Rule returns an API for working with rules. + Rule() nft.Rule +} diff --git a/contract/nft/chain.go b/contract/nft/chain.go new file mode 100644 index 0000000..2f6376d --- /dev/null +++ b/contract/nft/chain.go @@ -0,0 +1,46 @@ +package nft + +import ( + chain2 "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" +) + +// Chain for working with chains. +type Chain interface { + // Add adds a new chain. + // + // This command is equivalent to: + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|prerouting|forward|input|output|postrouting|egress) priority (priority_value = int32) ;}' + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type filter hook (forward|input|output) priority (priority_value = int32) ; policy (accept|drop) ;}' + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|egress) device {device} priority (priority_value = int32) ;}' + Add(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error + + // Create creates a new chain. + // Similar to the Add, but returns an error if the chain already exists. + // + // This command is equivalent to: + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|prerouting|forward|input|output|postrouting|egress) priority (priority_value = int32) ;}' + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type filter hook (forward|input|output) priority (priority_value = int32) ; policy (accept|drop) ;}' + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|egress) device {device} priority (priority_value = int32) ;}' + Create(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error + + // Delete deletes a chain. + // + // This command is equivalent to: + // nft delete chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + Delete(family family.Type, tableName string, chainName string) error + + // Clear clears all rules in a chain. + // + // This command is equivalent to: + // nft flush chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + Clear(family family.Type, tableName string, chainName string) error + + // Rename renames a chain. + // + // This command is equivalent to: + // nft rename chain (ip|ip6|inet|arp|bridge) {table_name} {old_chain_name} {new_chain_name} + Rename(family family.Type, tableName string, oldChainName string, newChainName string) error +} diff --git a/contract/nft/rule.go b/contract/nft/rule.go new file mode 100644 index 0000000..7ac7918 --- /dev/null +++ b/contract/nft/rule.go @@ -0,0 +1,31 @@ +package nft + +import "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + +// Rule is the interface for rule manipulation. +type Rule interface { + // Add adds a new rule. + // + // This command is equivalent to: + // nft add rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ expr }' + Add(family family.Type, tableName string, chainName string, expr ...string) error + + // Insert inserts a new rule. + // Inserted rules are placed at the beginning of the chain, by default. + // + // This command is equivalent to: + // nft insert rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ expr }' + Insert(family family.Type, tableName string, chainName string, expr ...string) error + + // Replace replaces a rule. + // + // This command is equivalent to: + // nft replace rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} {handle} '{ expr }' + Replace(family family.Type, tableName string, chainName string, handle uint64, expr ...string) error + + // Delete deletes a rule. + // + // This command is equivalent to: + // nft delete rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} {handle} + Delete(family family.Type, tableName string, chainName string, handle uint64) error +} diff --git a/contract/nft/table.go b/contract/nft/table.go new file mode 100644 index 0000000..d77f550 --- /dev/null +++ b/contract/nft/table.go @@ -0,0 +1,24 @@ +package nft + +import "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + +// Table for working with tables. +type Table interface { + // AddTable adds a new table. + // + // This command is equivalent to: + // nft add table (ip|ip6|inet|arp|bridge) {table_name} + Add(family family.Type, tableName string) error + + // DeleteTable deletes a table. + // + // This command is equivalent to: + // nft delete table (ip|ip6|inet|arp|bridge) {table_name} + Delete(family family.Type, tableName string) error + + // ClearTable clears all rules in a table. + // + // This command is equivalent to: + // nft flush table (ip|ip6|inet|arp|bridge) {table_name} + Clear(family family.Type, tableName string) error +} diff --git a/contract/nft/version.go b/contract/nft/version.go new file mode 100644 index 0000000..aa99ba7 --- /dev/null +++ b/contract/nft/version.go @@ -0,0 +1,8 @@ +package nft + +type Version interface { + // Version returns the version of the nftables client. + Version() string + // Opts returns the options of the nftables client. + Opts() map[string]string +} diff --git a/internal/chain/chain.go b/internal/chain/chain.go index 1a99b44..007ab58 100644 --- a/internal/chain/chain.go +++ b/internal/chain/chain.go @@ -2,55 +2,16 @@ package chain import ( chain2 "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" - "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/command" ) -// API for working with chains. -type API interface { - // Add adds a new chain. - // - // This command is equivalent to: - // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} - // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|prerouting|forward|input|output|postrouting|egress) priority (priority_value = int32) ;}' - // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type filter hook (forward|input|output) priority (priority_value = int32) ; policy (accept|drop) ;}' - // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|egress) device {device} priority (priority_value = int32) ;}' - Add(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error - - // Create creates a new chain. - // Similar to the Add, but returns an error if the chain already exists. - // - // This command is equivalent to: - // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} - // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|prerouting|forward|input|output|postrouting|egress) priority (priority_value = int32) ;}' - // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type filter hook (forward|input|output) priority (priority_value = int32) ; policy (accept|drop) ;}' - // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|egress) device {device} priority (priority_value = int32) ;}' - Create(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error - - // Delete deletes a chain. - // - // This command is equivalent to: - // nft delete chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} - Delete(family family.Type, tableName string, chainName string) error - - // Clear clears all rules in a chain. - // - // This command is equivalent to: - // nft flush chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} - Clear(family family.Type, tableName string, chainName string) error - - // Rename renames a chain. - // - // This command is equivalent to: - // nft rename chain (ip|ip6|inet|arp|bridge) {table_name} {old_chain_name} {new_chain_name} - Rename(family family.Type, tableName string, oldChainName string, newChainName string) error -} - type chain struct { - command command.NFT + command contract.Command } -func New(command command.NFT) API { +func New(command contract.Command) nft.Chain { return &chain{ command: command, } diff --git a/internal/command/command.go b/internal/command/command.go index e197b91..44adb28 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -3,21 +3,15 @@ package command import ( "errors" "os/exec" + + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" ) -type NFT interface { - // Run nft command. - Run(arg ...string) error - - // RunWithOutput Run nft command with output. - RunWithOutput(arg ...string) (string, error) -} - type execNFT struct { nftPath string } -func New(path string) (NFT, error) { +func New(path string) (contract.Command, error) { if err := checkingNFT(path); err != nil { return nil, err } diff --git a/internal/rule/rule.go b/internal/rule/rule.go index 3a90968..8ab2b51 100644 --- a/internal/rule/rule.go +++ b/internal/rule/rule.go @@ -3,42 +3,16 @@ package rule import ( "strconv" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" - "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/command" ) -type API interface { - // Add adds a new rule. - // - // This command is equivalent to: - // nft add rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ expr }' - Add(family family.Type, tableName string, chainName string, expr ...string) error - - // Insert inserts a new rule. - // Inserted rules are placed at the beginning of the chain, by default. - // - // This command is equivalent to: - // nft insert rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ expr }' - Insert(family family.Type, tableName string, chainName string, expr ...string) error - - // Replace replaces a rule. - // - // This command is equivalent to: - // nft replace rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} {handle} '{ expr }' - Replace(family family.Type, tableName string, chainName string, handle uint64, expr ...string) error - - // Delete deletes a rule. - // - // This command is equivalent to: - // nft delete rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} {handle} - Delete(family family.Type, tableName string, chainName string, handle uint64) error -} - type rule struct { - command command.NFT + command contract.Command } -func New(command command.NFT) API { +func New(command contract.Command) nft.Rule { return &rule{ command: command, } diff --git a/internal/table/table.go b/internal/table/table.go index 3ee1522..2309358 100644 --- a/internal/table/table.go +++ b/internal/table/table.go @@ -1,36 +1,16 @@ package table import ( + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" - "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/command" ) -// API for working with tables. -type API interface { - // AddTable adds a new table. - // - // This command is equivalent to: - // nft add table (ip|ip6|inet|arp|bridge) {table_name} - Add(family family.Type, tableName string) error - - // DeleteTable deletes a table. - // - // This command is equivalent to: - // nft delete table (ip|ip6|inet|arp|bridge) {table_name} - Delete(family family.Type, tableName string) error - - // ClearTable clears all rules in a table. - // - // This command is equivalent to: - // nft flush table (ip|ip6|inet|arp|bridge) {table_name} - Clear(family family.Type, tableName string) error -} - type table struct { - command command.NFT + command contract.Command } -func New(command command.NFT) API { +func New(command contract.Command) nft.Table { return &table{ command: command, } diff --git a/nft.go b/nft.go index d423a0a..505cbce 100644 --- a/nft.go +++ b/nft.go @@ -5,50 +5,24 @@ import ( "regexp" "strings" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + nftContract "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/chain" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/command" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/rule" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/table" ) -// NFT A client for working with nftables -type NFT interface { - // Command returns the command used to execute nft. - // You can execute your raw request. - Command() command.NFT - - // Clear clears all rules. - // - // This command is equivalent to: - // nft flush ruleset - Clear() error - - // Version returns the version of nftables. - // - // This command is equivalent to: - // nft -V - Version() (Version, error) - - // Table returns an API for working with tables. - Table() table.API - - // Chain returns an API for working with chains. - Chain() chain.API - - // Rule returns an API for working with rules. - Rule() rule.API -} - type nft struct { - command command.NFT - table table.API - chain chain.API - rule rule.API + command contract.Command + table nftContract.Table + chain nftContract.Chain + rule nftContract.Rule } // New Returns a client for working with nftables. // Searches for nft in paths: nft, /usr/sbin/nft, /sbin/nft -func New() (NFT, error) { +func New() (contract.NFT, error) { paths := []string{"nft", "/usr/sbin/nft", "/sbin/nft"} for _, path := range paths { nftClient, err := NewWithPath(path) @@ -61,7 +35,7 @@ func New() (NFT, error) { } // NewWithPath Returns the client for working with nftables with its path specified. -func NewWithPath(path string) (NFT, error) { +func NewWithPath(path string) (contract.NFT, error) { nftCommand, err := command.New(path) if err != nil { return nil, err @@ -80,7 +54,7 @@ func (n *nft) Clear() error { return n.command.Run(args...) } -func (n *nft) Version() (Version, error) { +func (n *nft) Version() (nftContract.Version, error) { args := []string{"-V"} out, err := n.command.RunWithOutput(args...) if err != nil { @@ -114,18 +88,18 @@ func (n *nft) Version() (Version, error) { }, nil } -func (n *nft) Table() table.API { +func (n *nft) Table() nftContract.Table { return n.table } -func (n *nft) Chain() chain.API { +func (n *nft) Chain() nftContract.Chain { return n.chain } -func (n *nft) Rule() rule.API { +func (n *nft) Rule() nftContract.Rule { return n.rule } -func (n *nft) Command() command.NFT { +func (n *nft) Command() contract.Command { return n.command } diff --git a/version.go b/version.go index 78c53fd..ef9c43a 100644 --- a/version.go +++ b/version.go @@ -1,21 +1,14 @@ package nft -type Version interface { - // Version returns the version of the nftables client. - Version() string - // Opts returns the options of the nftables client. - Opts() map[string]string -} - type version struct { version string opts map[string]string } -func (v version) Version() string { +func (v *version) Version() string { return v.version } -func (v version) Opts() map[string]string { +func (v *version) Opts() map[string]string { return v.opts } From a7ec1700960df9081b4ea0bbc7581a5eae7e302f Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Wed, 22 Apr 2026 23:34:43 +0500 Subject: [PATCH 02/22] Add Batch API for building and executing batched nftables commands. --- contract/batch.go | 31 +++++++++++++++++++++++++++ contract/batch/chain.go | 46 +++++++++++++++++++++++++++++++++++++++++ contract/batch/rule.go | 31 +++++++++++++++++++++++++++ contract/batch/table.go | 24 +++++++++++++++++++++ contract/nft.go | 3 +++ 5 files changed, 135 insertions(+) create mode 100644 contract/batch.go create mode 100644 contract/batch/chain.go create mode 100644 contract/batch/rule.go create mode 100644 contract/batch/table.go diff --git a/contract/batch.go b/contract/batch.go new file mode 100644 index 0000000..5a52d4e --- /dev/null +++ b/contract/batch.go @@ -0,0 +1,31 @@ +package contract + +import ( + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/batch" +) + +// BatchBuilder is an API for building a batch of commands. +type BatchBuilder interface { + // Clear clears all rules. + // + // This command is equivalent to: + // nft flush ruleset + Clear() error + + // Table returns an API for working with tables. + Table() batch.Table + + // Chain returns an API for working with chains. + Chain() batch.Chain + + // Rule returns an API for working with rules. + Rule() batch.Rule + + // Build returns a batch of commands. + Build() Batch +} + +type Batch interface { + // Args returns the arguments of the batch. + Args() []string +} diff --git a/contract/batch/chain.go b/contract/batch/chain.go new file mode 100644 index 0000000..60af687 --- /dev/null +++ b/contract/batch/chain.go @@ -0,0 +1,46 @@ +package batch + +import ( + chain2 "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" +) + +// Chain for working with chains. +type Chain interface { + // Add adds a new chain. + // + // This command is equivalent to: + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|prerouting|forward|input|output|postrouting|egress) priority (priority_value = int32) ;}' + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type filter hook (forward|input|output) priority (priority_value = int32) ; policy (accept|drop) ;}' + // nft add chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|egress) device {device} priority (priority_value = int32) ;}' + Add(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error + + // Create creates a new chain. + // Similar to the Add, but returns an error if the chain already exists. + // + // This command is equivalent to: + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|prerouting|forward|input|output|postrouting|egress) priority (priority_value = int32) ;}' + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type filter hook (forward|input|output) priority (priority_value = int32) ; policy (accept|drop) ;}' + // nft create chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ type (filter|route|nat) hook (ingress|egress) device {device} priority (priority_value = int32) ;}' + Create(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error + + // Delete deletes a chain. + // + // This command is equivalent to: + // nft delete chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + Delete(family family.Type, tableName string, chainName string) error + + // Clear clears all rules in a chain. + // + // This command is equivalent to: + // nft flush chain (ip|ip6|inet|arp|bridge) {table_name} {chain_name} + Clear(family family.Type, tableName string, chainName string) error + + // Rename renames a chain. + // + // This command is equivalent to: + // nft rename chain (ip|ip6|inet|arp|bridge) {table_name} {old_chain_name} {new_chain_name} + Rename(family family.Type, tableName string, oldChainName string, newChainName string) error +} diff --git a/contract/batch/rule.go b/contract/batch/rule.go new file mode 100644 index 0000000..9a9e577 --- /dev/null +++ b/contract/batch/rule.go @@ -0,0 +1,31 @@ +package batch + +import "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + +// Rule is the interface for rule manipulation. +type Rule interface { + // Add adds a new rule. + // + // This command is equivalent to: + // nft add rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ expr }' + Add(family family.Type, tableName string, chainName string, expr ...string) error + + // Insert inserts a new rule. + // Inserted rules are placed at the beginning of the chain, by default. + // + // This command is equivalent to: + // nft insert rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} '{ expr }' + Insert(family family.Type, tableName string, chainName string, expr ...string) error + + // Replace replaces a rule. + // + // This command is equivalent to: + // nft replace rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} {handle} '{ expr }' + Replace(family family.Type, tableName string, chainName string, handle uint64, expr ...string) error + + // Delete deletes a rule. + // + // This command is equivalent to: + // nft delete rule (ip|ip6|inet|arp|bridge) {table_name} {chain_name} {handle} + Delete(family family.Type, tableName string, chainName string, handle uint64) error +} diff --git a/contract/batch/table.go b/contract/batch/table.go new file mode 100644 index 0000000..dcbf5da --- /dev/null +++ b/contract/batch/table.go @@ -0,0 +1,24 @@ +package batch + +import "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + +// Table for working with tables. +type Table interface { + // AddTable adds a new table. + // + // This command is equivalent to: + // nft add table (ip|ip6|inet|arp|bridge) {table_name} + Add(family family.Type, tableName string) error + + // DeleteTable deletes a table. + // + // This command is equivalent to: + // nft delete table (ip|ip6|inet|arp|bridge) {table_name} + Delete(family family.Type, tableName string) error + + // ClearTable clears all rules in a table. + // + // This command is equivalent to: + // nft flush table (ip|ip6|inet|arp|bridge) {table_name} + Clear(family family.Type, tableName string) error +} diff --git a/contract/nft.go b/contract/nft.go index 28303e6..8b07196 100644 --- a/contract/nft.go +++ b/contract/nft.go @@ -8,6 +8,9 @@ type NFT interface { // You can execute your raw request. Command() Command + // ExecuteBatch executes a batch of commands. + ExecuteBatch(batch Batch) error + // Clear clears all rules. // // This command is equivalent to: From 70e6335e59b42fcc7cdfecc06c5a20aa8f16f3b2 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Wed, 22 Apr 2026 23:35:19 +0500 Subject: [PATCH 03/22] Add ExecuteBatch method to NFT interface for batch command execution --- nft.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nft.go b/nft.go index 505cbce..6ec9cb7 100644 --- a/nft.go +++ b/nft.go @@ -103,3 +103,7 @@ func (n *nft) Rule() nftContract.Rule { func (n *nft) Command() contract.Command { return n.command } + +func (n *nft) ExecuteBatch(batch contract.Batch) error { + return n.command.Run(batch.Args()...) +} From 94405fbb530063075c7eabe3a39406be511064c9 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:25:15 +0500 Subject: [PATCH 04/22] Add ExecuteBatchAfterCheck method to NFT interface for pre-checking and executing batch commands --- contract/nft.go | 3 +++ nft.go | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/contract/nft.go b/contract/nft.go index 8b07196..3005751 100644 --- a/contract/nft.go +++ b/contract/nft.go @@ -8,6 +8,9 @@ type NFT interface { // You can execute your raw request. Command() Command + // ExecuteBatchAfterCheck executes a batch of commands after checking the validity of the batch. + ExecuteBatchAfterCheck(batch Batch) error + // ExecuteBatch executes a batch of commands. ExecuteBatch(batch Batch) error diff --git a/nft.go b/nft.go index 6ec9cb7..899d85a 100644 --- a/nft.go +++ b/nft.go @@ -104,6 +104,13 @@ func (n *nft) Command() contract.Command { return n.command } +func (n *nft) ExecuteBatchAfterCheck(batch contract.Batch) error { + if err := batch.Check(n.command); err != nil { + return err + } + return n.command.Run(batch.Args()...) +} + func (n *nft) ExecuteBatch(batch contract.Batch) error { return n.command.Run(batch.Args()...) } From c4bd51d9bc99d5deec70a42aa246c5557b4041c5 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:25:50 +0500 Subject: [PATCH 05/22] Refactor Command interface to embed CommandRun for improved structure and consistency --- contract/command.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contract/command.go b/contract/command.go index ec8764e..61b862f 100644 --- a/contract/command.go +++ b/contract/command.go @@ -1,11 +1,12 @@ package contract -// Run is a function that executes nft command. -type Run func(arg ...string) error +type CommandRun interface { + // Run executes nft command. + Run(arg ...string) error +} type Command interface { - // Run nft command. - Run(arg ...string) error + CommandRun // RunWithOutput Run nft command with output. RunWithOutput(arg ...string) (string, error) From b36479c0aeb36c3a4062a09070a271a94b37c4da Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:26:15 +0500 Subject: [PATCH 06/22] Extend Batch interface with Close, Check, and MoveFile methods for enhanced functionality --- contract/batch.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contract/batch.go b/contract/batch.go index 5a52d4e..b4a0fe3 100644 --- a/contract/batch.go +++ b/contract/batch.go @@ -23,9 +23,21 @@ type BatchBuilder interface { // Build returns a batch of commands. Build() Batch + + // Close closes the batch. + Close() error } type Batch interface { // Args returns the arguments of the batch. Args() []string + + // Check checks the validity of the batch. + Check(command Command) error + + // Close closes the batch. + Close() error + + // MoveFile moves the batch file to the specified path. + MoveFile(path string) error } From 321f24e91540716e3ef67114f85bb59425ef1f75 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:26:47 +0500 Subject: [PATCH 07/22] Add File utility to create, write, move, and remove temporary files --- internal/pkg/file.go | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 internal/pkg/file.go diff --git a/internal/pkg/file.go b/internal/pkg/file.go new file mode 100644 index 0000000..42ec7d2 --- /dev/null +++ b/internal/pkg/file.go @@ -0,0 +1,51 @@ +package pkg + +import ( + "crypto/rand" + "os" + "path/filepath" + "time" +) + +type File struct { + filepath string + file *os.File + + isMoved bool +} + +func CreateRandomTmpFile(dir string) (*File, error) { + if err := os.MkdirAll(dir, 0750); err != nil { + return nil, err + } + tmpFile := filepath.Join(dir, time.Now().Format(time.RFC3339)+"_"+rand.Text()+".tmp") + file, err := os.Create(tmpFile) + if err != nil { + return nil, err + } + + return &File{ + filepath: tmpFile, + file: file, + }, nil +} + +func (f *File) Write(p []byte) (n int, err error) { + return f.file.Write(p) +} + +func (f *File) Path() string { + return f.filepath +} + +func (f *File) Remove() error { + if f.isMoved { + return nil + } + + return os.Remove(f.filepath) +} + +func (f *File) Move(path string) error { + return os.Rename(f.filepath, path) +} From 53d8854ab49500b323d9767a4946146570481288 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:27:11 +0500 Subject: [PATCH 08/22] Add CommandRun implementation for executing batch commands --- internal/batch/command.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 internal/batch/command.go diff --git a/internal/batch/command.go b/internal/batch/command.go new file mode 100644 index 0000000..e9cc9bf --- /dev/null +++ b/internal/batch/command.go @@ -0,0 +1,23 @@ +package batch + +import ( + "fmt" + + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg" +) + +type commandRun struct { + file *pkg.File +} + +func NewCommand(file *pkg.File) contract.CommandRun { + return &commandRun{ + file: file, + } +} + +func (c *commandRun) Run(args ...string) error { + _, err := c.file.Write([]byte(fmt.Sprintf("%s\n", args))) + return err +} From 6d62b280a1af578d971e3fe03ad236b78ea46153 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:27:31 +0500 Subject: [PATCH 09/22] Add Batch implementation with Args, Check, Close, and MoveFile methods --- internal/batch/batch.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 internal/batch/batch.go diff --git a/internal/batch/batch.go b/internal/batch/batch.go new file mode 100644 index 0000000..3c6541e --- /dev/null +++ b/internal/batch/batch.go @@ -0,0 +1,32 @@ +package batch + +import ( + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg" +) + +type batch struct { + file *pkg.File +} + +func NewBatch(file *pkg.File) contract.Batch { + return &batch{ + file: file, + } +} + +func (b *batch) Args() []string { + return []string{"-f", b.file.Path()} +} + +func (b *batch) Check(command contract.Command) error { + return command.Run("-c", "-f", b.file.Path()) +} + +func (b *batch) Close() error { + return b.file.Remove() +} + +func (b *batch) MoveFile(path string) error { + return b.file.Move(path) +} From 9cd17572b23ff93861752f08ed1fda544d2bb082 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:28:17 +0500 Subject: [PATCH 10/22] Add BatchBuilder for constructing and managing nftables batch commands --- batch.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 batch.go diff --git a/batch.go b/batch.go new file mode 100644 index 0000000..9dc87ad --- /dev/null +++ b/batch.go @@ -0,0 +1,72 @@ +package nft + +import ( + "fmt" + + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + contractBatch "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/batch" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/batch" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/batch/chain" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/batch/rule" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/batch/table" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg" +) + +type batchBuilder struct { + command contract.CommandRun + file *pkg.File + + table contractBatch.Table + chain contractBatch.Chain + rule contractBatch.Rule + + isBuildBatch bool +} + +func NewBatchBuilder(dir string) (contract.BatchBuilder, error) { + file, err := pkg.CreateRandomTmpFile(dir) + if err != nil { + return nil, err + } + + command := batch.NewCommand(file) + + return &batchBuilder{ + command: command, + file: file, + + table: table.New(command), + chain: chain.New(command), + rule: rule.New(command), + + isBuildBatch: false, + }, nil +} + +func (b *batchBuilder) Clear() error { + return fmt.Errorf("not implemented") +} + +func (b *batchBuilder) Table() contractBatch.Table { + return b.table +} + +func (b *batchBuilder) Chain() contractBatch.Chain { + return b.chain +} + +func (b *batchBuilder) Rule() contractBatch.Rule { + return b.rule +} + +func (b *batchBuilder) Build() contract.Batch { + b.isBuildBatch = true + return batch.NewBatch(b.file) +} + +func (b *batchBuilder) Close() error { + if b.isBuildBatch { + return nil + } + return b.file.Remove() +} From 8fb0306b1b1bd5f8e02bc2dd8a8dc5ff6ede9f8b Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:37:17 +0500 Subject: [PATCH 11/22] Switch from fmt.Sprintf to strings.Join for writing arguments in CommandRun --- internal/batch/command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/batch/command.go b/internal/batch/command.go index e9cc9bf..fcfc2fa 100644 --- a/internal/batch/command.go +++ b/internal/batch/command.go @@ -1,7 +1,7 @@ package batch import ( - "fmt" + "strings" "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg" @@ -18,6 +18,6 @@ func NewCommand(file *pkg.File) contract.CommandRun { } func (c *commandRun) Run(args ...string) error { - _, err := c.file.Write([]byte(fmt.Sprintf("%s\n", args))) + _, err := c.file.Write([]byte(strings.Join(args, " "))) return err } From 7d6dcf1ece58ebf324862afa7b5a344af5ff06e3 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:38:05 +0500 Subject: [PATCH 12/22] Refactor Clear method to use nftCommand.Clear for improved code reuse --- internal/pkg/nft/command.go | 5 +++++ nft.go | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/nft/command.go diff --git a/internal/pkg/nft/command.go b/internal/pkg/nft/command.go new file mode 100644 index 0000000..27e2146 --- /dev/null +++ b/internal/pkg/nft/command.go @@ -0,0 +1,5 @@ +package nft + +func Clear() []string { + return []string{"flush", "ruleset"} +} diff --git a/nft.go b/nft.go index 899d85a..bac25df 100644 --- a/nft.go +++ b/nft.go @@ -9,6 +9,7 @@ import ( nftContract "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/chain" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/command" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/rule" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/table" ) @@ -50,7 +51,7 @@ func NewWithPath(path string) (contract.NFT, error) { } func (n *nft) Clear() error { - args := []string{"flush", "ruleset"} + args := nftCommand.Clear() return n.command.Run(args...) } From 04b70ce5ec83d77b7bf15bfb7637664d11c63a4a Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:40:47 +0500 Subject: [PATCH 13/22] BatchBuilder implements the Clear method --- batch.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/batch.go b/batch.go index 9dc87ad..e46c63a 100644 --- a/batch.go +++ b/batch.go @@ -1,8 +1,6 @@ package nft import ( - "fmt" - "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" contractBatch "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/batch" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/batch" @@ -10,6 +8,7 @@ import ( "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/batch/rule" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/batch/table" "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" ) type batchBuilder struct { @@ -44,7 +43,8 @@ func NewBatchBuilder(dir string) (contract.BatchBuilder, error) { } func (b *batchBuilder) Clear() error { - return fmt.Errorf("not implemented") + args := nftCommand.Clear() + return b.command.Run(args...) } func (b *batchBuilder) Table() contractBatch.Table { From fedf0966bccd096c8cb70eb6b1f9466c5de0017d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:47:48 +0500 Subject: [PATCH 14/22] Refactor table commands to use nftCommand utilities for improved code reuse --- internal/pkg/nft/table.go | 15 +++++++++++++++ internal/table/table.go | 7 ++++--- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 internal/pkg/nft/table.go diff --git a/internal/pkg/nft/table.go b/internal/pkg/nft/table.go new file mode 100644 index 0000000..172a7c8 --- /dev/null +++ b/internal/pkg/nft/table.go @@ -0,0 +1,15 @@ +package nft + +import "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + +func TableAdd(family family.Type, tableName string) []string { + return []string{"add", "table", family.String(), tableName} +} + +func TableDelete(family family.Type, tableName string) []string { + return []string{"delete", "table", family.String(), tableName} +} + +func TableClear(family family.Type, tableName string) []string { + return []string{"flush", "table", family.String(), tableName} +} diff --git a/internal/table/table.go b/internal/table/table.go index 2309358..03b41bc 100644 --- a/internal/table/table.go +++ b/internal/table/table.go @@ -4,6 +4,7 @@ import ( "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" ) type table struct { @@ -17,16 +18,16 @@ func New(command contract.Command) nft.Table { } func (t *table) Add(family family.Type, tableName string) error { - args := []string{"add", "table", family.String(), tableName} + args := nftCommand.TableAdd(family, tableName) return t.command.Run(args...) } func (t *table) Delete(family family.Type, tableName string) error { - args := []string{"delete", "table", family.String(), tableName} + args := nftCommand.TableDelete(family, tableName) return t.command.Run(args...) } func (t *table) Clear(family family.Type, tableName string) error { - args := []string{"flush", "table", family.String(), tableName} + args := nftCommand.TableClear(family, tableName) return t.command.Run(args...) } From a56dcc2952d5917db04c1543c5e4ad21d346133d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:58:38 +0500 Subject: [PATCH 15/22] Switch to fmt.Sprintf for appending a newline in CommandRun for consistency --- internal/batch/command.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/batch/command.go b/internal/batch/command.go index fcfc2fa..57590f6 100644 --- a/internal/batch/command.go +++ b/internal/batch/command.go @@ -1,6 +1,7 @@ package batch import ( + "fmt" "strings" "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" @@ -18,6 +19,6 @@ func NewCommand(file *pkg.File) contract.CommandRun { } func (c *commandRun) Run(args ...string) error { - _, err := c.file.Write([]byte(strings.Join(args, " "))) + _, err := c.file.Write([]byte(fmt.Sprintf("%s\n", strings.Join(args, " ")))) return err } From 8fdecbcd8d180058d6f9f49158836854562f1270 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 15:59:53 +0500 Subject: [PATCH 16/22] Add table implementation with Add, Delete, and Clear methods --- internal/batch/table/table.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 internal/batch/table/table.go diff --git a/internal/batch/table/table.go b/internal/batch/table/table.go new file mode 100644 index 0000000..a24a686 --- /dev/null +++ b/internal/batch/table/table.go @@ -0,0 +1,33 @@ +package table + +import ( + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" +) + +type table struct { + command contract.CommandRun +} + +func New(command contract.CommandRun) nft.Table { + return &table{ + command: command, + } +} + +func (t *table) Add(family family.Type, tableName string) error { + args := nftCommand.TableAdd(family, tableName) + return t.command.Run(args...) +} + +func (t *table) Delete(family family.Type, tableName string) error { + args := nftCommand.TableDelete(family, tableName) + return t.command.Run(args...) +} + +func (t *table) Clear(family family.Type, tableName string) error { + args := nftCommand.TableClear(family, tableName) + return t.command.Run(args...) +} From 7dc8436ad8ca69778c45e53091a1426b47e0595d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 16:05:16 +0500 Subject: [PATCH 17/22] Refactor chain commands to use nftCommand utilities for improved code reuse --- internal/chain/chain.go | 11 ++++++----- internal/pkg/nft/chain.go | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 internal/pkg/nft/chain.go diff --git a/internal/chain/chain.go b/internal/chain/chain.go index 007ab58..bfe13fc 100644 --- a/internal/chain/chain.go +++ b/internal/chain/chain.go @@ -5,6 +5,7 @@ import ( "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" ) type chain struct { @@ -18,26 +19,26 @@ func New(command contract.Command) nft.Chain { } func (c *chain) Add(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error { - args := []string{"add", "chain", family.String(), tableName, chainName, baseChain.String()} + args := nftCommand.ChainAdd(family, tableName, chainName, baseChain) return c.command.Run(args...) } func (c *chain) Create(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error { - args := []string{"create", "chain", family.String(), tableName, chainName, baseChain.String()} + args := nftCommand.ChainCreate(family, tableName, chainName, baseChain) return c.command.Run(args...) } func (c *chain) Delete(family family.Type, tableName string, chainName string) error { - args := []string{"delete", "chain", family.String(), tableName, chainName} + args := nftCommand.ChainDelete(family, tableName, chainName) return c.command.Run(args...) } func (c *chain) Clear(family family.Type, tableName string, chainName string) error { - args := []string{"flush", "chain", family.String(), tableName, chainName} + args := nftCommand.ChainClear(family, tableName, chainName) return c.command.Run(args...) } func (c *chain) Rename(family family.Type, tableName string, oldChainName string, newChainName string) error { - args := []string{"rename", "chain", family.String(), tableName, oldChainName, newChainName} + args := nftCommand.ChainRename(family, tableName, oldChainName, newChainName) return c.command.Run(args...) } diff --git a/internal/pkg/nft/chain.go b/internal/pkg/nft/chain.go new file mode 100644 index 0000000..d44d0d8 --- /dev/null +++ b/internal/pkg/nft/chain.go @@ -0,0 +1,26 @@ +package nft + +import ( + chain2 "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" +) + +func ChainAdd(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) []string { + return []string{"add", "chain", family.String(), tableName, chainName, baseChain.String()} +} + +func ChainCreate(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) []string { + return []string{"create", "chain", family.String(), tableName, chainName, baseChain.String()} +} + +func ChainDelete(family family.Type, tableName string, chainName string) []string { + return []string{"delete", "chain", family.String(), tableName, chainName} +} + +func ChainClear(family family.Type, tableName string, chainName string) []string { + return []string{"flush", "chain", family.String(), tableName, chainName} +} + +func ChainRename(family family.Type, tableName string, oldChainName string, newChainName string) []string { + return []string{"rename", "chain", family.String(), tableName, oldChainName, newChainName} +} From bc00e4686565eef058615b57cdff5c1a2fa896af Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 16:05:58 +0500 Subject: [PATCH 18/22] Add chain implementation with Add, Create, Delete, Clear, and Rename methods --- internal/batch/chain/chain.go | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 internal/batch/chain/chain.go diff --git a/internal/batch/chain/chain.go b/internal/batch/chain/chain.go new file mode 100644 index 0000000..04c37c4 --- /dev/null +++ b/internal/batch/chain/chain.go @@ -0,0 +1,44 @@ +package chain + +import ( + chain2 "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" +) + +type chain struct { + command contract.CommandRun +} + +func New(command contract.CommandRun) nft.Chain { + return &chain{ + command: command, + } +} + +func (c *chain) Add(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error { + args := nftCommand.ChainAdd(family, tableName, chainName, baseChain) + return c.command.Run(args...) +} + +func (c *chain) Create(family family.Type, tableName string, chainName string, baseChain chain2.ChainOptions) error { + args := nftCommand.ChainCreate(family, tableName, chainName, baseChain) + return c.command.Run(args...) +} + +func (c *chain) Delete(family family.Type, tableName string, chainName string) error { + args := nftCommand.ChainDelete(family, tableName, chainName) + return c.command.Run(args...) +} + +func (c *chain) Clear(family family.Type, tableName string, chainName string) error { + args := nftCommand.ChainClear(family, tableName, chainName) + return c.command.Run(args...) +} + +func (c *chain) Rename(family family.Type, tableName string, oldChainName string, newChainName string) error { + args := nftCommand.ChainRename(family, tableName, oldChainName, newChainName) + return c.command.Run(args...) +} From 8d9c3e7f7b7642cf74d8a0020cd134c46ac7900f Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 16:12:24 +0500 Subject: [PATCH 19/22] Refactor rule commands to use nftCommand utilities for improved code reuse --- internal/pkg/nft/rule.go | 30 ++++++++++++++++++++++++++++++ internal/rule/rule.go | 14 +++++--------- 2 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 internal/pkg/nft/rule.go diff --git a/internal/pkg/nft/rule.go b/internal/pkg/nft/rule.go new file mode 100644 index 0000000..178edc2 --- /dev/null +++ b/internal/pkg/nft/rule.go @@ -0,0 +1,30 @@ +package nft + +import ( + "strconv" + + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" +) + +func RuleAdd(family family.Type, tableName string, chainName string, expr ...string) []string { + args := []string{"add", "rule", family.String(), tableName, chainName} + args = append(args, expr...) + return args +} + +func RuleInsert(family family.Type, tableName string, chainName string, expr ...string) []string { + args := []string{"insert", "rule", family.String(), tableName, chainName} + args = append(args, expr...) + return args +} + +func RuleReplace(family family.Type, tableName string, chainName string, handle uint64, expr ...string) []string { + args := []string{"replace", "rule", family.String(), tableName, chainName, "handle", strconv.Itoa(int(handle))} + args = append(args, expr...) + return args +} + +func RuleDelete(family family.Type, tableName string, chainName string, handle uint64) []string { + args := []string{"delete", "rule", family.String(), tableName, chainName, "handle", strconv.Itoa(int(handle))} + return args +} diff --git a/internal/rule/rule.go b/internal/rule/rule.go index 8ab2b51..1a072a5 100644 --- a/internal/rule/rule.go +++ b/internal/rule/rule.go @@ -1,11 +1,10 @@ package rule import ( - "strconv" - "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" ) type rule struct { @@ -19,24 +18,21 @@ func New(command contract.Command) nft.Rule { } func (r *rule) Add(family family.Type, tableName string, chainName string, expr ...string) error { - args := []string{"add", "rule", family.String(), tableName, chainName} - args = append(args, expr...) + args := nftCommand.RuleAdd(family, tableName, chainName, expr...) return r.command.Run(args...) } func (r *rule) Insert(family family.Type, tableName string, chainName string, expr ...string) error { - args := []string{"insert", "rule", family.String(), tableName, chainName} - args = append(args, expr...) + args := nftCommand.RuleInsert(family, tableName, chainName, expr...) return r.command.Run(args...) } func (r *rule) Replace(family family.Type, tableName string, chainName string, handle uint64, expr ...string) error { - args := []string{"replace", "rule", family.String(), tableName, chainName, "handle", strconv.Itoa(int(handle))} - args = append(args, expr...) + args := nftCommand.RuleReplace(family, tableName, chainName, handle, expr...) return r.command.Run(args...) } func (r *rule) Delete(family family.Type, tableName string, chainName string, handle uint64) error { - args := []string{"delete", "rule", family.String(), tableName, chainName, "handle", strconv.Itoa(int(handle))} + args := nftCommand.RuleDelete(family, tableName, chainName, handle) return r.command.Run(args...) } From 2037ac6034157df14a8e5025b14ca96dda840683 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 16:13:09 +0500 Subject: [PATCH 20/22] Add rule implementation with Add, Insert, Replace, and Delete methods --- internal/batch/rule/rule.go | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 internal/batch/rule/rule.go diff --git a/internal/batch/rule/rule.go b/internal/batch/rule/rule.go new file mode 100644 index 0000000..1f7bd98 --- /dev/null +++ b/internal/batch/rule/rule.go @@ -0,0 +1,38 @@ +package rule + +import ( + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract/nft" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" +) + +type rule struct { + command contract.CommandRun +} + +func New(command contract.CommandRun) nft.Rule { + return &rule{ + command: command, + } +} + +func (r *rule) Add(family family.Type, tableName string, chainName string, expr ...string) error { + args := nftCommand.RuleAdd(family, tableName, chainName, expr...) + return r.command.Run(args...) +} + +func (r *rule) Insert(family family.Type, tableName string, chainName string, expr ...string) error { + args := nftCommand.RuleInsert(family, tableName, chainName, expr...) + return r.command.Run(args...) +} + +func (r *rule) Replace(family family.Type, tableName string, chainName string, handle uint64, expr ...string) error { + args := nftCommand.RuleReplace(family, tableName, chainName, handle, expr...) + return r.command.Run(args...) +} + +func (r *rule) Delete(family family.Type, tableName string, chainName string, handle uint64) error { + args := nftCommand.RuleDelete(family, tableName, chainName, handle) + return r.command.Run(args...) +} From 692ba5fba05c9d80caa6cafac75d5ac36a07f9dc Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 16:27:46 +0500 Subject: [PATCH 21/22] Add Command method to BatchBuilder interface and implementation --- batch.go | 4 ++++ contract/batch.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/batch.go b/batch.go index e46c63a..5f9b6e0 100644 --- a/batch.go +++ b/batch.go @@ -42,6 +42,10 @@ func NewBatchBuilder(dir string) (contract.BatchBuilder, error) { }, nil } +func (b *batchBuilder) Command() contract.CommandRun { + return b.command +} + func (b *batchBuilder) Clear() error { args := nftCommand.Clear() return b.command.Run(args...) diff --git a/contract/batch.go b/contract/batch.go index b4a0fe3..0a3f871 100644 --- a/contract/batch.go +++ b/contract/batch.go @@ -6,6 +6,9 @@ import ( // BatchBuilder is an API for building a batch of commands. type BatchBuilder interface { + // Command returns the command to run. + Command() CommandRun + // Clear clears all rules. // // This command is equivalent to: From 7ae37d00ae68c46463f4286b050157511c0cf08b Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 26 Apr 2026 16:48:51 +0500 Subject: [PATCH 22/22] Add batch commands support and usage example in README --- README.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8f75399..3999875 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Go-низкоуровневая обёртка для управления [nfta - Абстракции для работы с IP/IP6/inet/arp/bridge/netdev families - Интерфейс для выполнения CLI-команд nftables напрямую - Простой и минималистичный API для быстрой интеграции +- Добавлена возможность формировать batch-команды и отправлять их в nftables одним вызовом. ## Установка ```sh @@ -29,13 +30,13 @@ import ( ) func main() { - nft, err := nft.New() + nftClient, err := nft.New() if err != nil { log.Fatalf("nft not found: %v", err) } // Добавить таблицу - if err := nft.Table().Add(family.IP, "test"); err != nil { + if err := nftClient.Table().Add(family.IP, "test"); err != nil { log.Fatalf("table add failed: %v", err) } @@ -43,16 +44,78 @@ func main() { chainType.Hook = chain.HookOutput chainType.Priority = 0 chainType.Policy = chain.PolicyAccept - if err := nft.Chain().Add(family.IP, "test", "test", chainType); err != nil { + if err := nftClient.Chain().Add(family.IP, "test", "test", chainType); err != nil { log.Fatalf("table add failed: %v", err) } // Добавить правило (пример: дропать пакеты от 1.2.3.4) - if err := nft.Rule().Add(family.IP, "test", "test", "ip", "saddr", "1.2.3.4", "drop"); err != nil { + if err := nftClient.Rule().Add(family.IP, "test", "test", "ip", "saddr", "1.2.3.4", "drop"); err != nil { log.Fatalf("rule add failed: %v", err) } } ``` +### Batch commands + +Библиотека поддерживает отправку команд в `nft` через batch-файлы. + +Это позволяет заранее сформировать набор команд, например для таблиц, цепочек и правил, +а затем выполнить их одной операцией. + +## Пример использования + +```go +package main + +import ( + "log" + "git.kor-elf.net/kor-elf-shield/go-nftables-client" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/family" + "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain" +) + +func main() { + nftClient, err := nft.New() + if err != nil { + log.Fatalf("nft not found: %v", err) + } + + batchBuilder, err := nft.NewBatchBuilder("/tmp/nft_batch") + if err != nil { + log.Fatalf("batch builder init failed: %v", err) + } + defer func() { + _ = batchBuilder.Close() + }() + + // Добавить таблицу + if err := batchBuilder.Table().Add(family.IP, "test"); err != nil { + log.Fatalf("table add failed: %v", err) + } + + chainType := chain.NewBaseChainOptions(chain.TypeFilter) + chainType.Hook = chain.HookOutput + chainType.Priority = 0 + chainType.Policy = chain.PolicyAccept + if err := batchBuilder.Chain().Add(family.IP, "test", "test", chainType); err != nil { + log.Fatalf("table add failed: %v", err) + } + + // Добавить правило (пример: дропать пакеты от 1.2.3.4) + if err := batchBuilder.Rule().Add(family.IP, "test", "test", "ip", "saddr", "1.2.3.4", "drop"); err != nil { + log.Fatalf("rule add failed: %v", err) + } + + batch := batchBuilder.Build() + defer func() { + _ = batch.Close() + }() + + // Выполняет пакет команд после проверки их корректности + if err := nftClient.ExecuteBatchAfterCheck(batch); err != nil { + log.Fatalf("batch execute failed: %v", err) + } +} +``` ## Краткое описание API @@ -76,6 +139,12 @@ nft.Rule().Delete(family family.Type, table, chain string, handle uint64) error nft.Clear() error // flush ruleset nft.Version() (Version, error) // информация о версии nftables nft.Command() command.NFT // ручное выполнение любых nft-команд + +// ExecuteBatchAfterCheck Выполняет пакет команд после проверки их корректности +nft.ExecuteBatchAfterCheck(batch Batch) error + +// ExecuteBatch Выполняет пакет команд +nft.ExecuteBatch(batch Batch) error ``` ## Требования