From e7e53fc1232db0ec4c11fdbf8579bef92d35fbb3 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Wed, 22 Oct 2025 20:56:46 +0500 Subject: [PATCH] Add Chain API for managing nftables chains, including Add, Create, Delete, Clear, and Rename operations. --- chain/hook.go | 36 ++++++++++++++++++ chain/policy.go | 21 +++++++++++ chain/type.go | 68 ++++++++++++++++++++++++++++++++++ internal/chain/chain.go | 82 +++++++++++++++++++++++++++++++++++++++++ nft.go | 3 ++ 5 files changed, 210 insertions(+) create mode 100644 chain/hook.go create mode 100644 chain/policy.go create mode 100644 chain/type.go create mode 100644 internal/chain/chain.go diff --git a/chain/hook.go b/chain/hook.go new file mode 100644 index 0000000..a1f0252 --- /dev/null +++ b/chain/hook.go @@ -0,0 +1,36 @@ +package chain + +import "fmt" + +type Hook int8 + +const ( + HookInput Hook = iota + HookOutput + HookForward + HookPrerouting + HookPostrouting + HookIngress + HookEgress +) + +func (h Hook) String() string { + switch h { + case HookInput: + return "input" + case HookOutput: + return "output" + case HookForward: + return "forward" + case HookPrerouting: + return "prerouting" + case HookPostrouting: + return "postrouting" + case HookIngress: + return "ingress" + case HookEgress: + return "egress" + default: + return fmt.Sprintf("unknown hook %d", h) + } +} diff --git a/chain/policy.go b/chain/policy.go new file mode 100644 index 0000000..6a15d94 --- /dev/null +++ b/chain/policy.go @@ -0,0 +1,21 @@ +package chain + +import "fmt" + +type Policy int8 + +const ( + PolicyAccept Policy = iota + 1 + PolicyDrop +) + +func (p Policy) String() string { + switch p { + case PolicyAccept: + return "accept" + case PolicyDrop: + return "drop" + default: + return fmt.Sprintf("unknown policy %d", p) + } +} diff --git a/chain/type.go b/chain/type.go new file mode 100644 index 0000000..df5506a --- /dev/null +++ b/chain/type.go @@ -0,0 +1,68 @@ +package chain + +import ( + "fmt" + "strconv" +) + +type ChainOptions interface { + String() string +} + +type Type int8 + +const ( + TypeNone Type = iota + TypeFilter + TypeNat + TypeRoute +) + +func (t Type) String() string { + switch t { + case TypeNone: + return "" + case TypeFilter: + return "filter" + case TypeNat: + return "inet" + case TypeRoute: + return "nat" + default: + return fmt.Sprintf("unknown type %d", t) + } +} + +type BaseChainOptions struct { + Type Type + Hook Hook + Priority int32 + Policy Policy + Device string +} + +func (b BaseChainOptions) String() string { + if b.Type == TypeNone { + return "" + } + + device := "" + if b.Hook == HookEgress || b.Hook == HookIngress { + if b.Device != "" { + device = " device " + b.Device + " " + } + } + + policy := "" + if b.Type == TypeFilter { + policy = "policy " + b.Policy.String() + " ; " + } + + return "{ type " + b.Type.String() + " hook " + b.Hook.String() + " " + device + " priority " + strconv.Itoa(int(b.Priority)) + " ; " + policy + " }" +} + +func NewBaseChainOptions(t Type) BaseChainOptions { + return BaseChainOptions{ + Type: t, + } +} diff --git a/internal/chain/chain.go b/internal/chain/chain.go new file mode 100644 index 0000000..1a99b44 --- /dev/null +++ b/internal/chain/chain.go @@ -0,0 +1,82 @@ +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/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 +} + +func New(command command.NFT) API { + 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()} + 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()} + 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} + 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} + 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} + return c.command.Run(args...) +} diff --git a/nft.go b/nft.go index 195f4a3..2e4b86d 100644 --- a/nft.go +++ b/nft.go @@ -26,6 +26,9 @@ type NFT interface { // Table returns an API for working with tables. Table() table.API + + // Chain returns an API for working with chains. + Chain() chain.API } type nft struct {