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 ``` ## Требования diff --git a/batch.go b/batch.go new file mode 100644 index 0000000..5f9b6e0 --- /dev/null +++ b/batch.go @@ -0,0 +1,76 @@ +package nft + +import ( + "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" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" +) + +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) Command() contract.CommandRun { + return b.command +} + +func (b *batchBuilder) Clear() error { + args := nftCommand.Clear() + return b.command.Run(args...) +} + +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() +} diff --git a/contract/batch.go b/contract/batch.go new file mode 100644 index 0000000..0a3f871 --- /dev/null +++ b/contract/batch.go @@ -0,0 +1,46 @@ +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 { + // Command returns the command to run. + Command() CommandRun + + // 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 + + // 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 +} 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/command.go b/contract/command.go new file mode 100644 index 0000000..61b862f --- /dev/null +++ b/contract/command.go @@ -0,0 +1,13 @@ +package contract + +type CommandRun interface { + // Run executes nft command. + Run(arg ...string) error +} + +type Command interface { + CommandRun + + // 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..3005751 --- /dev/null +++ b/contract/nft.go @@ -0,0 +1,37 @@ +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 + + // 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 + + // 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/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) +} 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...) +} diff --git a/internal/batch/command.go b/internal/batch/command.go new file mode 100644 index 0000000..57590f6 --- /dev/null +++ b/internal/batch/command.go @@ -0,0 +1,24 @@ +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" +) + +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", strings.Join(args, " ")))) + return err +} 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...) +} 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...) +} diff --git a/internal/chain/chain.go b/internal/chain/chain.go index 1a99b44..bfe13fc 100644 --- a/internal/chain/chain.go +++ b/internal/chain/chain.go @@ -2,81 +2,43 @@ 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" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" ) -// 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, } } 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/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/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) +} 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} +} 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/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/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/rule/rule.go b/internal/rule/rule.go index 3a90968..1a072a5 100644 --- a/internal/rule/rule.go +++ b/internal/rule/rule.go @@ -1,68 +1,38 @@ 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" + nftCommand "git.kor-elf.net/kor-elf-shield/go-nftables-client/internal/pkg/nft" ) -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, } } 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...) } diff --git a/internal/table/table.go b/internal/table/table.go index 3ee1522..03b41bc 100644 --- a/internal/table/table.go +++ b/internal/table/table.go @@ -1,52 +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" - "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" ) -// 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, } } 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...) } diff --git a/nft.go b/nft.go index d423a0a..bac25df 100644 --- a/nft.go +++ b/nft.go @@ -5,50 +5,25 @@ 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" + 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" ) -// 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 +36,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 @@ -76,11 +51,11 @@ func NewWithPath(path string) (NFT, error) { } func (n *nft) Clear() error { - args := []string{"flush", "ruleset"} + args := nftCommand.Clear() 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 +89,29 @@ 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 } + +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()...) +} 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 }