Compare commits

14 Commits

Author SHA1 Message Date
6c95d5efb1 Merge pull request 'Версия 0.1.0' (#1) from develop into main
Reviewed-on: #1
2025-10-22 23:10:41 +05:00
7360c05f9b Add initial README with usage examples and API documentation 2025-10-22 23:01:32 +05:00
a305b7f55a Fix type conversion by removing unnecessary string conversion for command output parsing 2025-10-22 22:44:23 +05:00
2ac9272293 Add Rule API for managing nftables rules, including Add, Insert, Replace, and Delete operations 2025-10-22 22:43:33 +05:00
90b232aa05 Add Command method to NFT interface and RunWithOutput support to command package 2025-10-22 20:59:49 +05:00
e7e53fc123 Add Chain API for managing nftables chains, including Add, Create, Delete, Clear, and Rename operations. 2025-10-22 20:56:46 +05:00
09ac999346 Add NETDEV type to Type enum and update String method 2025-10-20 22:45:13 +05:00
9210448f16 Add Version method to NFT interface and implementation
Include `RunWithOutput` support in the command package and introduce version parsing logic, enabling retrieval of the nftables version and options.
2025-10-20 22:44:40 +05:00
c3a513f92c Improve documentation for API interface in table package. 2025-10-19 22:54:04 +05:00
5b19993343 Refactor table management to use a dedicated API and improve command handling. 2025-10-19 22:50:14 +05:00
d056f5dbf8 Changed the name parameter to nameTable in methods for working with tables. 2025-10-19 21:12:25 +05:00
1a62968d1d Add ClearTable method to NFT interface and implementation. 2025-10-19 20:25:36 +05:00
053ca9a37f Add DeleteTable method to NFT interface and implementation. 2025-10-19 20:11:45 +05:00
7cd41f8491 Improve documentation for Clear and AddTable methods in NFT interface. 2025-10-19 19:55:42 +05:00
12 changed files with 586 additions and 29 deletions

88
README.md Normal file
View File

@@ -0,0 +1,88 @@
# go-nftables-client
Go-низкоуровневая обёртка для управления [nftables](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page) через командную строку.
## Возможности
- Добавление и удаление таблиц (`add table`, `delete table`)
- Добавление и удаление цепочек (`add chain`, `delete chain`, настройка hook/policy)
- Добавление, удаление, очистка правил (`add rule`, `delete rule`, `flush`)
- Абстракции для работы с IP/IP6/inet/arp/bridge/netdev families
- Интерфейс для выполнения CLI-команд nftables напрямую
- Простой и минималистичный API для быстрой интеграции
## Установка
```sh
go get git.kor-elf.net/kor-elf-shield/go-nftables-client
```
## Пример использования
```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() {
nft, err := nft.New()
if err != nil {
log.Fatalf("nft not found: %v", err)
}
// Добавить таблицу
if err := nft.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 := nft.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 {
log.Fatalf("rule add failed: %v", err)
}
}
```
## Краткое описание API
```go
nft.Table().Add(family.Type, tableName string) error
nft.Table().Delete(family.Type, tableName string) error
nft.Table().Clear(family.Type, tableName string) error
nft.Chain().Add(family.Type, table, chain string, baseChain chain.ChainOptions) error
nft.Chain().Create(family family.Type, table, chain string, baseChain chain.ChainOptions) error
nft.Chain().Rename(family family.Type, table, oldChainName, newChainName string) error
nft.Chain().Delete(family.Type, table, chain string) error
nft.Chain().Clear(family.Type, table, chain string) error
nft.Rule().Add(family.Type, table, chain string, expr ...string) error
nft.Rule().Insert(family family.Type, table, chain string, expr ...string) error
nft.Rule().Replace(family family.Type, table, chain string, handle uint64, expr ...string) error
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-команд
```
## Требования
- Go 1.25+
- Установленный nft (`/usr/sbin/nft`, `/sbin/nft` или доступен через PATH)
## Лицензия
[MIT](https://git.kor-elf.net/kor-elf-shield/go-nftables-client/src/branch/main/LICENSE)

36
chain/hook.go Normal file
View File

@@ -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)
}
}

21
chain/policy.go Normal file
View File

@@ -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)
}
}

68
chain/type.go Normal file
View File

@@ -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,
}
}

View File

@@ -1,19 +1,20 @@
package nft
package family
import "fmt"
type FamilyType int8
type Type int8
const (
IP FamilyType = iota + 1
IP Type = iota + 1
IP6
INET
ARP
BRIDGE
NETDEV
)
func (f FamilyType) String() string {
switch f {
func (t Type) String() string {
switch t {
case IP:
return "ip"
case IP6:
@@ -24,7 +25,9 @@ func (f FamilyType) String() string {
return "arp"
case BRIDGE:
return "bridge"
case NETDEV:
return "netdev"
default:
return fmt.Sprintf("Encoding(%d)", f)
return fmt.Sprintf("unknown family %d", t)
}
}

82
internal/chain/chain.go Normal file
View File

@@ -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...)
}

View File

@@ -0,0 +1,53 @@
package command
import (
"errors"
"os/exec"
)
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) {
if err := checkingNFT(path); err != nil {
return nil, err
}
return &execNFT{
nftPath: path,
}, nil
}
func (r *execNFT) Run(arg ...string) error {
cmd := exec.Command(r.nftPath, arg...)
out, err := cmd.CombinedOutput()
if err != nil {
if len(out) > 0 {
return errors.New(string(out))
}
return err
}
return nil
}
func (r *execNFT) RunWithOutput(arg ...string) (string, error) {
cmd := exec.Command(r.nftPath, arg...)
out, err := cmd.CombinedOutput()
if err != nil {
if len(out) > 0 {
return string(out), err
}
return "", err
}
return string(out), nil
}

View File

@@ -1,4 +1,4 @@
package nft
package command
import (
"errors"
@@ -8,19 +8,6 @@ import (
"strings"
)
func executeCommand(name string, arg ...string) error {
cmd := exec.Command(name, arg...)
out, err := cmd.CombinedOutput()
if err != nil {
if len(out) > 0 {
return errors.New(string(out))
}
return err
}
return nil
}
func checkingNFT(path string) error {
if path == "" {
return errors.New("path is empty")

68
internal/rule/rule.go Normal file
View File

@@ -0,0 +1,68 @@
package rule
import (
"strconv"
"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
}
func New(command command.NFT) API {
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...)
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...)
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...)
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))}
return r.command.Run(args...)
}

52
internal/table/table.go Normal file
View File

@@ -0,0 +1,52 @@
package table
import (
"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
}
func New(command command.NFT) API {
return &table{
command: command,
}
}
func (t *table) Add(family family.Type, tableName string) error {
args := []string{"add", "table", family.String(), tableName}
return t.command.Run(args...)
}
func (t *table) Delete(family family.Type, tableName string) error {
args := []string{"delete", "table", family.String(), tableName}
return t.command.Run(args...)
}
func (t *table) Clear(family family.Type, tableName string) error {
args := []string{"flush", "table", family.String(), tableName}
return t.command.Run(args...)
}

96
nft.go
View File

@@ -2,19 +2,48 @@ package nft
import (
"errors"
"regexp"
"strings"
"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
// AddTable adds a new table.
AddTable(family FamilyType, name string) 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 {
path string
command command.NFT
table table.API
chain chain.API
rule rule.API
}
// New Returns a client for working with nftables.
@@ -33,21 +62,70 @@ func New() (NFT, error) {
// NewWithPath Returns the client for working with nftables with its path specified.
func NewWithPath(path string) (NFT, error) {
if err := checkingNFT(path); err != nil {
nftCommand, err := command.New(path)
if err != nil {
return nil, err
}
return &nft{
path: path,
command: nftCommand,
table: table.New(nftCommand),
chain: chain.New(nftCommand),
rule: rule.New(nftCommand),
}, nil
}
func (n *nft) Clear() error {
args := []string{"flush", "ruleset"}
return executeCommand(n.path, args...)
return n.command.Run(args...)
}
func (n *nft) AddTable(family FamilyType, name string) error {
args := []string{"add", "table", family.String(), name}
return executeCommand(n.path, args...)
func (n *nft) Version() (Version, error) {
args := []string{"-V"}
out, err := n.command.RunWithOutput(args...)
if err != nil {
return nil, err
}
vers := ""
opts := make(map[string]string)
lines := regexp.MustCompile("\r?\n").Split(strings.TrimSpace(out), -1)
for index, line := range lines {
line = strings.TrimSpace(line)
if index == 0 {
vers = line
continue
}
values := strings.Split(line, ":")
if len(values) != 2 {
continue
}
name := strings.TrimSpace(values[0])
value := strings.TrimSpace(values[1])
opts[name] = value
}
return &version{
version: vers,
opts: opts,
}, nil
}
func (n *nft) Table() table.API {
return n.table
}
func (n *nft) Chain() chain.API {
return n.chain
}
func (n *nft) Rule() rule.API {
return n.rule
}
func (n *nft) Command() command.NFT {
return n.command
}

21
version.go Normal file
View File

@@ -0,0 +1,21 @@
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 {
return v.version
}
func (v version) Opts() map[string]string {
return v.opts
}