mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-02-18 18:28:18 -05:00
Update knftables to v0.0.20
This commit is contained in:
parent
870e2928bc
commit
62c3d8d820
11 changed files with 1267 additions and 226 deletions
2
go.mod
2
go.mod
|
|
@ -115,7 +115,7 @@ require (
|
|||
k8s.io/system-validators v1.12.1
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730
|
||||
sigs.k8s.io/knftables v0.0.17
|
||||
sigs.k8s.io/knftables v0.0.20
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -508,8 +508,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2
|
|||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/knftables v0.0.17 h1:wGchTyRF/iGTIjd+vRaR1m676HM7jB8soFtyr/148ic=
|
||||
sigs.k8s.io/knftables v0.0.17/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk=
|
||||
sigs.k8s.io/knftables v0.0.20 h1:eU2NWpgcJ/wgb4Fy0cX3klK6nDjERvZRdYgkORLU0Tc=
|
||||
sigs.k8s.io/knftables v0.0.20/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk=
|
||||
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
|
||||
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.20.1/go.mod h1:R7rQ8kxknVlXWVUIbxWtMgu8DCCNVtl8V0KrmeVd/KE=
|
||||
|
|
|
|||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
|
@ -1207,7 +1207,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client
|
|||
## explicit; go 1.23
|
||||
sigs.k8s.io/json
|
||||
sigs.k8s.io/json/internal/golang/encoding/json
|
||||
# sigs.k8s.io/knftables v0.0.17
|
||||
# sigs.k8s.io/knftables v0.0.20
|
||||
## explicit; go 1.20
|
||||
sigs.k8s.io/knftables
|
||||
# sigs.k8s.io/kustomize/api v0.20.1
|
||||
|
|
|
|||
55
vendor/sigs.k8s.io/knftables/CHANGELOG.md
generated
vendored
55
vendor/sigs.k8s.io/knftables/CHANGELOG.md
generated
vendored
|
|
@ -1,5 +1,60 @@
|
|||
# ChangeLog
|
||||
|
||||
## v0.0.20
|
||||
|
||||
- `List()` has been changed to use `nft list table` rather than, e.g.,
|
||||
`nft list sets`, to ensure that it doesn't try to parse objects in
|
||||
other tables (which may have been created by newer versions of `nft`
|
||||
and might trigger crashes in older versions of `nft`; see
|
||||
https://issues.k8s.io/136786). (`@danwinship` based on a previous PR
|
||||
from `@kairosci`).
|
||||
|
||||
- A new `ListAll()` method has been added to help work around the fact
|
||||
that `List()` is now much less efficient with large tables.
|
||||
(`@danwinship`).
|
||||
|
||||
- `ListElements()` now correctly handles maps/sets with concatenated
|
||||
keys/values including CIDR values. (`@danwinship`)
|
||||
|
||||
## v0.0.19
|
||||
|
||||
- Added the ability to use a single `knftables.Interface` (and a
|
||||
single `knftables.Transaction`) with multiple tables/families. To do
|
||||
this, pass `""` for the family and table name to `knftables.New`,
|
||||
and then manually fill in the `Table` and `Family` fields in all
|
||||
`Object`s you create. (`@danwinship`)
|
||||
|
||||
- Added `tx.Destroy()`, corresponding to `nft destroy`. Since `nft
|
||||
destroy` requires a new-ish kernel (6.3) and CLI (1.0.8), there are
|
||||
also two new `knftables.New()` options: `RequireDestroy` if you want
|
||||
construction to fail on older systems, or `EmulateDestroy` if you
|
||||
want knftables to try to emulate "destroy" on older systems, with
|
||||
some limitations. See [README.md](./README.md#destroy-operations)
|
||||
for more details. (`@danwinship`)
|
||||
|
||||
- Added `Counter` objects and the `tx.Reset()` verb, to support
|
||||
nftables counters. (`@aroradaman`)
|
||||
|
||||
- Added `Table.Flags` and `Chain.Policy`. (Note that at this time the
|
||||
"owner" and "persist" table flags can't usefully be used with
|
||||
knftables, since knftables opens a new connection to the kernel for
|
||||
each transaction and so the table would become un-owned immediately
|
||||
after it was created.) (`@danwinship`)
|
||||
|
||||
- Fixed `Fake.ParseDump()` to correctly parse rules with raw payload
|
||||
expressions (`@danwinship`) and `flow add` rules (`hongliangl`).
|
||||
|
||||
## v0.0.18
|
||||
|
||||
- Added locking to `Fake` to allow it to be safely used concurrently.
|
||||
(`@npinaeva`)
|
||||
|
||||
- Added a `Flowtable` object, and `Fake` support for correctly parsing
|
||||
flowtable references. (`@aojea`)
|
||||
|
||||
- Fixed a bug in `Fake.ParseDump`, which accidentally required the
|
||||
table to have a comment. (`@danwinship`)
|
||||
|
||||
## v0.0.17
|
||||
|
||||
- `ListRules()` now accepts `""` for the chain name, meaning to list
|
||||
|
|
|
|||
2
vendor/sigs.k8s.io/knftables/CONTRIBUTING.md
generated
vendored
2
vendor/sigs.k8s.io/knftables/CONTRIBUTING.md
generated
vendored
|
|
@ -25,4 +25,4 @@ If your repo has certain guidelines for contribution, put them here ahead of the
|
|||
knftables is maintained by [Kubernetes SIG Network](https://github.com/kubernetes/community/tree/master/sig-network).
|
||||
|
||||
- [sig-network slack channel](https://kubernetes.slack.com/messages/sig-network)
|
||||
- [kubernetes-sig-network mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-network)
|
||||
- [kubernetes-sig-network mailing list](https://groups.google.com/a/kubernetes.io/g/sig-network)
|
||||
|
|
|
|||
116
vendor/sigs.k8s.io/knftables/README.md
generated
vendored
116
vendor/sigs.k8s.io/knftables/README.md
generated
vendored
|
|
@ -51,32 +51,42 @@ if err != nil {
|
|||
}
|
||||
```
|
||||
|
||||
(If you want to operate on multiple tables or multiple nftables
|
||||
families, you will need separate `Interface` objects for each. If you
|
||||
need to check whether the system supports an nftables feature as with
|
||||
`nft --check`, use `nft.Check()`, which works the same as `nft.Run()`
|
||||
below.)
|
||||
`knftables.New` also takes a comma-separated list of options after the
|
||||
family and table name; see the documentation for that function for
|
||||
more information.
|
||||
|
||||
You can use the `List`, `ListRules`, and `ListElements` methods on the
|
||||
`Interface` to check if objects exist. `List` returns the names of
|
||||
`"chains"`, `"sets"`, or `"maps"` in the table, while `ListElements`
|
||||
returns `Element` objects and `ListRules` returns *partial* `Rule`
|
||||
objects.
|
||||
(If you want to operate on multiple tables or multiple nftables
|
||||
families, you have two options: you can either create separate
|
||||
`Interface` objects for each table, or you can create a single
|
||||
`Interface` and pass `""` for the family and table. In that case, you
|
||||
will need to explicitly fill in the `Family` and `Table` fields of
|
||||
every `Chain`, `Rule`, etc, object you create.)
|
||||
|
||||
You can use the various `List*` methods on the `Interface` to check if
|
||||
objects exist. `ListAll` returns a map of the names of top-level
|
||||
objects in the table, sorted by object type, while `List` returns just
|
||||
the names of objects of a single type. `ListElements`, `ListRules`,
|
||||
and `ListCounters` returned parsed objects of the given types. Note
|
||||
that `ListRules` returns *partial* `Rule` objects; it does not fill in
|
||||
the `Rule` field.
|
||||
|
||||
```golang
|
||||
chains, err := nft.List(ctx, "chains")
|
||||
allChains, err := nft.List(ctx, "chains")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list chains: %v", err)
|
||||
}
|
||||
for chain := range sets.New(allChains...).Difference(expectedChains) {
|
||||
tx.Delete(&knftables.Chain{Name: chain})
|
||||
}
|
||||
|
||||
FIXME
|
||||
// ...
|
||||
|
||||
elements, err := nft.ListElements(ctx, "map", "mymap")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list map elements: %v", err)
|
||||
}
|
||||
|
||||
FIXME
|
||||
...
|
||||
```
|
||||
|
||||
To make changes, create a `Transaction`, add the appropriate
|
||||
|
|
@ -116,17 +126,81 @@ methods to check for those well-known error types. In a large
|
|||
transaction, there is no supported way to determine exactly which
|
||||
operation failed.
|
||||
|
||||
(You can also pass a transaction to `nft.Check()`, which uses `nft
|
||||
--check`, but otherwise behaves the same as `nft.Run()`.)
|
||||
|
||||
## `knftables.Transaction` operations
|
||||
|
||||
`knftables.Transaction` operations correspond to the top-level commands
|
||||
in the `nft` binary. Currently-supported operations are:
|
||||
|
||||
- `tx.Add()`: adds an object, which may already exist, as with `nft add`
|
||||
- `tx.Add()`: creates an object if it does not already exist, as with `nft add`
|
||||
- `tx.Create()`: creates an object, which must not already exist, as with `nft create`
|
||||
- `tx.Flush()`: flushes the contents of a table/chain/set/map, as with `nft flush`
|
||||
- `tx.Delete()`: deletes an object, as with `nft delete`
|
||||
- `tx.Insert()`: inserts a rule before another rule, as with `nft insert rule`
|
||||
- `tx.Reset()`: resets a counter, as with `nft reset`
|
||||
- `tx.Delete()`: deletes an object, which must exist, as with `nft delete`
|
||||
- `tx.Destroy()`: deletes an object if it exists, as with `nft destroy`
|
||||
|
||||
For `Rule` objects the semantics and operations are slightly different:
|
||||
|
||||
- `tx.Add()`: appends a rule to a chain or adds it after an existing rule, as with `nft add rule`
|
||||
- `tx.Insert()`: prepends a rule to a chain or inserts it before another rule, as with `nft insert rule`
|
||||
- `tx.Replace()`: replaces a rule, as with `nft replace rule`
|
||||
- `tx.Delete()`/`tx.Destroy()`: deletes the rule with the given `Handle`, as with `nft delete rule`/`nft destroy rule`
|
||||
|
||||
### `Destroy` operations
|
||||
|
||||
Actually doing `nft destroy` requires a fairly new kernel (6.3 or
|
||||
later) and `nft` binary (1.0.8 or later). Trying to run a transaction
|
||||
containing a `Destroy` operation on an older host will result in an
|
||||
error.
|
||||
|
||||
There are two construct-time options to help out with this. First, you
|
||||
can specify `RequireDestroy`, if you want knftables construction to
|
||||
fail on older hosts:
|
||||
|
||||
```golang
|
||||
nft, err := knftables.New(knftables.IPv4Family, "my-table", knftables.RequireDestroy)
|
||||
if err != nil {
|
||||
...
|
||||
```
|
||||
|
||||
Alternatively, you can construct the `Interface` with the
|
||||
`EmulateDestroy` option:
|
||||
|
||||
```golang
|
||||
nft, err := knftables.New(knftables.IPv4Family, "my-table", knftables.EmulateDestroy)
|
||||
```
|
||||
|
||||
in which case knftables will attempt to emulate `nft destroy` if it is
|
||||
not available by doing a combination of an `add` and a `delete` (where
|
||||
the `add` will succeed whether the object previously existed or not,
|
||||
and then the `delete` will succeed because the object definitely
|
||||
exists at that point). To ensure that this emulation will work, if
|
||||
`EmulateDestroy` is in effect then `tx.Destroy()` will require that
|
||||
you pass it an object that is suitable for passing to both `tx.Add()`
|
||||
and `tx.Delete()` (even if the system you are currently on supports
|
||||
`nft destroy`). In particular, this means that when `EmulateDestroy`
|
||||
is in effect:
|
||||
|
||||
- You can only `Destroy()` objects by `Name` or `Key`, not by
|
||||
`Handle`.
|
||||
|
||||
- You can't `Destroy()` a `Rule` (since `Rule`s can only be deleted
|
||||
by `Handle`).
|
||||
|
||||
- If you include optional fields in the object (e.g. base chain
|
||||
properties), they need to be correct (since an `Add()` would fail
|
||||
if you passed different values). However, note that you *can* just
|
||||
leave the optional fields unset.
|
||||
|
||||
- When `Destroy()`ing a `Set` or `Map` you must include the correct
|
||||
`Type` (since an `Add()` would fail if you did not specify it or
|
||||
specified it incorrectly).
|
||||
|
||||
- When `Destroy()`ing a `Map` `Element` you must include the correct
|
||||
`Value` (since an `Add()` would fail if you did not specify it or
|
||||
specified it incorrectly).
|
||||
|
||||
## Objects
|
||||
|
||||
|
|
@ -134,11 +208,13 @@ The `Transaction` methods take arguments of type `knftables.Object`.
|
|||
The currently-supported objects are:
|
||||
|
||||
- `Table`
|
||||
- `Flowtable`
|
||||
- `Chain`
|
||||
- `Rule`
|
||||
- `Set`
|
||||
- `Map`
|
||||
- `Element`
|
||||
- `Counter`
|
||||
|
||||
Optional fields in objects can be filled in with the help of the
|
||||
`PtrTo()` function, which just returns a pointer to its argument.
|
||||
|
|
@ -159,8 +235,7 @@ the current state of the fake nftables database.
|
|||
|
||||
## Missing APIs
|
||||
|
||||
Various top-level object types are not yet supported (notably the
|
||||
"stateful objects" like `counter`).
|
||||
Various top-level object types are not yet supported.
|
||||
|
||||
Most IPTables libraries have an API for "add this rule only if it
|
||||
doesn't already exist", but that does not seem as useful in nftables
|
||||
|
|
@ -170,11 +245,6 @@ tend to have static rules and dynamic sets/maps, rather than having
|
|||
dynamic rules. If you aren't sure if a chain has the correct rules,
|
||||
you can just `Flush` it and recreate all of the rules.
|
||||
|
||||
The "destroy" (delete-without-ENOENT) command that exists in newer
|
||||
versions of `nft` is not currently supported because it would be
|
||||
unexpectedly heavyweight to emulate on systems that don't have it, so
|
||||
it is better (for now) to force callers to implement it by hand.
|
||||
|
||||
`ListRules` returns `Rule` objects without the `Rule` field filled in,
|
||||
because it uses the JSON API to list the rules, but there is no easy
|
||||
way to convert the JSON rule representation back into plaintext form.
|
||||
|
|
|
|||
396
vendor/sigs.k8s.io/knftables/fake.go
generated
vendored
396
vendor/sigs.k8s.io/knftables/fake.go
generated
vendored
|
|
@ -23,20 +23,33 @@ import (
|
|||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Fake is a fake implementation of Interface
|
||||
type Fake struct {
|
||||
nftContext
|
||||
// mutex is used to protect Table/Tables and LastTransaction.
|
||||
// When Table/Tables and LastTransaction are accessed directly, the caller must
|
||||
// acquire Fake.RLock and release when finished.
|
||||
sync.RWMutex
|
||||
|
||||
nextHandle int
|
||||
|
||||
// Table contains the Interface's table. This will be `nil` until you `tx.Add()`
|
||||
// the table.
|
||||
// Table contains the Interface's table (assuming the Fake has a default table).
|
||||
// This will be `nil` until you `tx.Add()` the table.
|
||||
// Make sure to acquire Fake.RLock before accessing Table in a concurrent environment.
|
||||
Table *FakeTable
|
||||
|
||||
// Tables contains all tables known to Fake. This will be empty until you
|
||||
// `tx.Add()` a table.
|
||||
// Make sure to acquire Fake.RLock before accessing Tables in a concurrent environment.
|
||||
Tables map[Family]map[string]*FakeTable
|
||||
|
||||
// LastTransaction is the last transaction passed to Run(). It will remain set until the
|
||||
// next time Run() is called. (It is not affected by Check().)
|
||||
// Make sure to acquire Fake.RLock before accessing LastTransaction in a
|
||||
// concurrent environment.
|
||||
LastTransaction *Transaction
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +57,9 @@ type Fake struct {
|
|||
type FakeTable struct {
|
||||
Table
|
||||
|
||||
// Flowtables contains the table's flowtables, keyed by name
|
||||
Flowtables map[string]*FakeFlowtable
|
||||
|
||||
// Chains contains the table's chains, keyed by name
|
||||
Chains map[string]*FakeChain
|
||||
|
||||
|
|
@ -52,6 +68,19 @@ type FakeTable struct {
|
|||
|
||||
// Maps contains the table's maps, keyed by name
|
||||
Maps map[string]*FakeMap
|
||||
|
||||
// Counters contains the table's counters, keyed by name
|
||||
Counters map[string]*FakeCounter
|
||||
}
|
||||
|
||||
// FakeFlowtable wraps Flowtable for the Fake implementation
|
||||
type FakeFlowtable struct {
|
||||
Flowtable
|
||||
}
|
||||
|
||||
// FakeCounter wraps Counter for the Fake implementation
|
||||
type FakeCounter struct {
|
||||
Counter
|
||||
}
|
||||
|
||||
// FakeChain wraps Chain for the Fake implementation
|
||||
|
|
@ -82,6 +111,11 @@ type FakeMap struct {
|
|||
|
||||
// NewFake creates a new fake Interface, for unit tests
|
||||
func NewFake(family Family, table string) *Fake {
|
||||
if (family == "") != (table == "") {
|
||||
// NewFake doesn't have an error return value, so...
|
||||
panic("family and table must either both be specified or both be empty")
|
||||
}
|
||||
|
||||
return &Fake{
|
||||
nftContext: nftContext{
|
||||
family: family,
|
||||
|
|
@ -92,8 +126,39 @@ func NewFake(family Family, table string) *Fake {
|
|||
|
||||
var _ Interface = &Fake{}
|
||||
|
||||
// ListAll is part of Interface.
|
||||
func (fake *Fake) ListAll(_ context.Context) (map[string][]string, error) {
|
||||
fake.RLock()
|
||||
defer fake.RUnlock()
|
||||
if fake.Table == nil {
|
||||
return nil, notFoundError("no such table %q", fake.table)
|
||||
}
|
||||
|
||||
result := make(map[string][]string)
|
||||
|
||||
for name := range fake.Table.Flowtables {
|
||||
result["flowtable"] = append(result["flowtable"], name)
|
||||
}
|
||||
for name := range fake.Table.Chains {
|
||||
result["chain"] = append(result["chain"], name)
|
||||
}
|
||||
for name := range fake.Table.Sets {
|
||||
result["set"] = append(result["set"], name)
|
||||
}
|
||||
for name := range fake.Table.Maps {
|
||||
result["map"] = append(result["map"], name)
|
||||
}
|
||||
for name := range fake.Table.Counters {
|
||||
result["counter"] = append(result["counter"], name)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// List is part of Interface.
|
||||
func (fake *Fake) List(_ context.Context, objectType string) ([]string, error) {
|
||||
fake.RLock()
|
||||
defer fake.RUnlock()
|
||||
if fake.Table == nil {
|
||||
return nil, notFoundError("no such table %q", fake.table)
|
||||
}
|
||||
|
|
@ -101,6 +166,10 @@ func (fake *Fake) List(_ context.Context, objectType string) ([]string, error) {
|
|||
var result []string
|
||||
|
||||
switch objectType {
|
||||
case "flowtable", "flowtables":
|
||||
for name := range fake.Table.Flowtables {
|
||||
result = append(result, name)
|
||||
}
|
||||
case "chain", "chains":
|
||||
for name := range fake.Table.Chains {
|
||||
result = append(result, name)
|
||||
|
|
@ -113,6 +182,10 @@ func (fake *Fake) List(_ context.Context, objectType string) ([]string, error) {
|
|||
for name := range fake.Table.Maps {
|
||||
result = append(result, name)
|
||||
}
|
||||
case "counter", "counters":
|
||||
for name := range fake.Table.Counters {
|
||||
result = append(result, name)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported object type %q", objectType)
|
||||
|
|
@ -123,6 +196,8 @@ func (fake *Fake) List(_ context.Context, objectType string) ([]string, error) {
|
|||
|
||||
// ListRules is part of Interface
|
||||
func (fake *Fake) ListRules(_ context.Context, chain string) ([]*Rule, error) {
|
||||
fake.RLock()
|
||||
defer fake.RUnlock()
|
||||
if fake.Table == nil {
|
||||
return nil, notFoundError("no such table %q", fake.table)
|
||||
}
|
||||
|
|
@ -145,6 +220,8 @@ func (fake *Fake) ListRules(_ context.Context, chain string) ([]*Rule, error) {
|
|||
|
||||
// ListElements is part of Interface
|
||||
func (fake *Fake) ListElements(_ context.Context, objectType, name string) ([]*Element, error) {
|
||||
fake.RLock()
|
||||
defer fake.RUnlock()
|
||||
if fake.Table == nil {
|
||||
return nil, notFoundError("no such %s %q", objectType, name)
|
||||
}
|
||||
|
|
@ -169,69 +246,119 @@ func (fake *Fake) NewTransaction() *Transaction {
|
|||
|
||||
// Run is part of Interface
|
||||
func (fake *Fake) Run(_ context.Context, tx *Transaction) error {
|
||||
fake.Lock()
|
||||
defer fake.Unlock()
|
||||
fake.LastTransaction = tx
|
||||
updatedTable, err := fake.run(tx)
|
||||
updatedTables, err := fake.run(tx)
|
||||
if err == nil {
|
||||
fake.Table = updatedTable
|
||||
fake.Tables = updatedTables
|
||||
if fake.family != "" && fake.table != "" {
|
||||
fake.Table = updatedTables[fake.family][fake.table]
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Check is part of Interface
|
||||
func (fake *Fake) Check(_ context.Context, tx *Transaction) error {
|
||||
fake.RLock()
|
||||
defer fake.RUnlock()
|
||||
_, err := fake.run(tx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
||||
// must be called with fake.lock held
|
||||
func (fake *Fake) run(tx *Transaction) (map[Family]map[string]*FakeTable, error) {
|
||||
if tx.err != nil {
|
||||
return nil, tx.err
|
||||
}
|
||||
|
||||
updatedTable := fake.Table.copy()
|
||||
for _, op := range tx.operations {
|
||||
// If the table hasn't been created, and this isn't a Table operation, then fail
|
||||
if updatedTable == nil {
|
||||
if _, ok := op.obj.(*Table); !ok {
|
||||
return nil, notFoundError("no such table \"%s %s\"", fake.family, fake.table)
|
||||
}
|
||||
updatedTables := make(map[Family]map[string]*FakeTable)
|
||||
for family := range fake.Tables {
|
||||
updatedTables[family] = make(map[string]*FakeTable)
|
||||
for name, table := range fake.Tables[family] {
|
||||
updatedTables[family][name] = table.copy()
|
||||
}
|
||||
}
|
||||
|
||||
for _, op := range tx.operations {
|
||||
if op.verb == addVerb || op.verb == createVerb || op.verb == insertVerb {
|
||||
fake.nextHandle++
|
||||
}
|
||||
|
||||
switch obj := op.obj.(type) {
|
||||
case *Table:
|
||||
err := checkExists(op.verb, "table", fake.table, updatedTable != nil)
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Name)
|
||||
table := updatedTables[family][tableName]
|
||||
err := checkExists(op.verb, "table", fake.table, table != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch op.verb {
|
||||
case flushVerb:
|
||||
updatedTable = nil
|
||||
table = nil
|
||||
fallthrough
|
||||
case addVerb, createVerb:
|
||||
if updatedTable != nil {
|
||||
if table != nil {
|
||||
continue
|
||||
}
|
||||
table := *obj
|
||||
table.Handle = PtrTo(fake.nextHandle)
|
||||
updatedTable = &FakeTable{
|
||||
Table: table,
|
||||
Chains: make(map[string]*FakeChain),
|
||||
Sets: make(map[string]*FakeSet),
|
||||
Maps: make(map[string]*FakeMap),
|
||||
table = &FakeTable{
|
||||
Table: *obj,
|
||||
Flowtables: make(map[string]*FakeFlowtable),
|
||||
Chains: make(map[string]*FakeChain),
|
||||
Sets: make(map[string]*FakeSet),
|
||||
Maps: make(map[string]*FakeMap),
|
||||
Counters: make(map[string]*FakeCounter),
|
||||
}
|
||||
case deleteVerb:
|
||||
updatedTable = nil
|
||||
table.Handle = PtrTo(fake.nextHandle)
|
||||
if updatedTables[family] == nil {
|
||||
updatedTables[family] = make(map[string]*FakeTable)
|
||||
}
|
||||
updatedTables[family][tableName] = table
|
||||
case deleteVerb, destroyVerb:
|
||||
if table != nil {
|
||||
delete(updatedTables[family], tableName)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
|
||||
case *Flowtable:
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Table)
|
||||
table, err := fake.checkTable(updatedTables, family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingFlowtable := table.Flowtables[obj.Name]
|
||||
err = checkExists(op.verb, "flowtable", obj.Name, existingFlowtable != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch op.verb {
|
||||
case addVerb, createVerb:
|
||||
if existingFlowtable != nil {
|
||||
continue
|
||||
}
|
||||
flowtable := *obj
|
||||
flowtable.Handle = PtrTo(fake.nextHandle)
|
||||
table.Flowtables[obj.Name] = &FakeFlowtable{
|
||||
Flowtable: flowtable,
|
||||
}
|
||||
case deleteVerb, destroyVerb:
|
||||
// FIXME delete-by-handle
|
||||
delete(table.Flowtables, obj.Name)
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
|
||||
case *Chain:
|
||||
existingChain := updatedTable.Chains[obj.Name]
|
||||
err := checkExists(op.verb, "chain", obj.Name, existingChain != nil)
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Table)
|
||||
table, err := fake.checkTable(updatedTables, family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingChain := table.Chains[obj.Name]
|
||||
err = checkExists(op.verb, "chain", obj.Name, existingChain != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -242,20 +369,25 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
|||
}
|
||||
chain := *obj
|
||||
chain.Handle = PtrTo(fake.nextHandle)
|
||||
updatedTable.Chains[obj.Name] = &FakeChain{
|
||||
table.Chains[obj.Name] = &FakeChain{
|
||||
Chain: chain,
|
||||
}
|
||||
case flushVerb:
|
||||
existingChain.Rules = nil
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
// FIXME delete-by-handle
|
||||
delete(updatedTable.Chains, obj.Name)
|
||||
delete(table.Chains, obj.Name)
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
|
||||
case *Rule:
|
||||
existingChain := updatedTable.Chains[obj.Chain]
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Table)
|
||||
table, err := fake.checkTable(updatedTables, family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingChain := table.Chains[obj.Chain]
|
||||
if existingChain == nil {
|
||||
return nil, notFoundError("no such chain %q", obj.Chain)
|
||||
}
|
||||
|
|
@ -282,7 +414,7 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
|||
refRule = *obj.Index
|
||||
}
|
||||
|
||||
if err := checkRuleRefs(obj, updatedTable); err != nil {
|
||||
if err := checkRuleRefs(obj, table); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -308,8 +440,13 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
|||
}
|
||||
|
||||
case *Set:
|
||||
existingSet := updatedTable.Sets[obj.Name]
|
||||
err := checkExists(op.verb, "set", obj.Name, existingSet != nil)
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Table)
|
||||
table, err := fake.checkTable(updatedTables, family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingSet := table.Sets[obj.Name]
|
||||
err = checkExists(op.verb, "set", obj.Name, existingSet != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -320,20 +457,25 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
|||
}
|
||||
set := *obj
|
||||
set.Handle = PtrTo(fake.nextHandle)
|
||||
updatedTable.Sets[obj.Name] = &FakeSet{
|
||||
table.Sets[obj.Name] = &FakeSet{
|
||||
Set: set,
|
||||
}
|
||||
case flushVerb:
|
||||
existingSet.Elements = nil
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
// FIXME delete-by-handle
|
||||
delete(updatedTable.Sets, obj.Name)
|
||||
delete(table.Sets, obj.Name)
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
case *Map:
|
||||
existingMap := updatedTable.Maps[obj.Name]
|
||||
err := checkExists(op.verb, "map", obj.Name, existingMap != nil)
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Table)
|
||||
table, err := fake.checkTable(updatedTables, family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingMap := table.Maps[obj.Name]
|
||||
err = checkExists(op.verb, "map", obj.Name, existingMap != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -344,20 +486,25 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
|||
}
|
||||
mapObj := *obj
|
||||
mapObj.Handle = PtrTo(fake.nextHandle)
|
||||
updatedTable.Maps[obj.Name] = &FakeMap{
|
||||
table.Maps[obj.Name] = &FakeMap{
|
||||
Map: mapObj,
|
||||
}
|
||||
case flushVerb:
|
||||
existingMap.Elements = nil
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
// FIXME delete-by-handle
|
||||
delete(updatedTable.Maps, obj.Name)
|
||||
delete(table.Maps, obj.Name)
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
case *Element:
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Table)
|
||||
table, err := fake.checkTable(updatedTables, family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if obj.Set != "" {
|
||||
existingSet := updatedTable.Sets[obj.Set]
|
||||
existingSet := table.Sets[obj.Set]
|
||||
if existingSet == nil {
|
||||
return nil, notFoundError("no such set %q", obj.Set)
|
||||
}
|
||||
|
|
@ -372,22 +519,22 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
|||
} else {
|
||||
existingSet.Elements = append(existingSet.Elements, &element)
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
element := *obj
|
||||
if i := findElement(existingSet.Elements, element.Key); i != -1 {
|
||||
existingSet.Elements = append(existingSet.Elements[:i], existingSet.Elements[i+1:]...)
|
||||
} else {
|
||||
} else if op.verb == deleteVerb {
|
||||
return nil, notFoundError("no such element %q", strings.Join(element.Key, " . "))
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
} else {
|
||||
existingMap := updatedTable.Maps[obj.Map]
|
||||
existingMap := table.Maps[obj.Map]
|
||||
if existingMap == nil {
|
||||
return nil, notFoundError("no such map %q", obj.Map)
|
||||
}
|
||||
if err := checkElementRefs(obj, updatedTable); err != nil {
|
||||
if err := checkElementRefs(obj, table); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch op.verb {
|
||||
|
|
@ -401,28 +548,84 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
|
|||
} else {
|
||||
existingMap.Elements = append(existingMap.Elements, &element)
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
element := *obj
|
||||
if i := findElement(existingMap.Elements, element.Key); i != -1 {
|
||||
existingMap.Elements = append(existingMap.Elements[:i], existingMap.Elements[i+1:]...)
|
||||
} else {
|
||||
} else if op.verb == deleteVerb {
|
||||
return nil, notFoundError("no such element %q", strings.Join(element.Key, " . "))
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
}
|
||||
case *Counter:
|
||||
family, tableName, _ := getTable(&fake.nftContext, obj.Family, obj.Table)
|
||||
table, err := fake.checkTable(updatedTables, family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingCounter := table.Counters[obj.Name]
|
||||
switch op.verb {
|
||||
case addVerb, createVerb:
|
||||
err := checkExists(op.verb, "counter", obj.Name, existingCounter != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingCounter != nil {
|
||||
continue
|
||||
}
|
||||
obj.Handle = PtrTo(fake.nextHandle)
|
||||
table.Counters[obj.Name] = &FakeCounter{*obj}
|
||||
case resetVerb:
|
||||
err := checkExists(op.verb, "counter", obj.Name, existingCounter != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
table.Counters[obj.Name].Packets = PtrTo[uint64](0)
|
||||
table.Counters[obj.Name].Bytes = PtrTo[uint64](0)
|
||||
case deleteVerb:
|
||||
if obj.Handle != nil {
|
||||
var found bool
|
||||
for _, counter := range table.Counters {
|
||||
if *counter.Handle == *obj.Handle {
|
||||
found = true
|
||||
delete(table.Counters, counter.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, notFoundError("no such counter %q", obj.Name)
|
||||
}
|
||||
} else {
|
||||
err := checkExists(op.verb, "counter", obj.Name, existingCounter != nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(table.Counters, obj.Name)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operation %q", op.verb)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled object type %T", op.obj)
|
||||
}
|
||||
}
|
||||
|
||||
return updatedTable, nil
|
||||
return updatedTables, nil
|
||||
}
|
||||
|
||||
func (fake *Fake) checkTable(updatedTables map[Family]map[string]*FakeTable, family Family, tableName string) (*FakeTable, error) {
|
||||
table := updatedTables[family][tableName]
|
||||
if table == nil {
|
||||
return nil, notFoundError("no such table \"%s\" \"%s\"", family, tableName)
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func checkExists(verb verb, objectType, name string, exists bool) error {
|
||||
switch verb {
|
||||
case addVerb:
|
||||
case addVerb, destroyVerb:
|
||||
// It's fine if the object either exists or doesn't
|
||||
return nil
|
||||
case createVerb:
|
||||
|
|
@ -441,12 +644,16 @@ func checkExists(verb verb, objectType, name string, exists bool) error {
|
|||
func checkRuleRefs(rule *Rule, table *FakeTable) error {
|
||||
words := strings.Split(rule.Rule, " ")
|
||||
for i, word := range words {
|
||||
if strings.HasPrefix(word, "@") {
|
||||
if strings.HasPrefix(word, "@") && !strings.Contains(word, ",") {
|
||||
name := word[1:]
|
||||
if i > 0 && (words[i] == "map" || words[i] == "vmap") {
|
||||
if i > 0 && (words[i-1] == "map" || words[i-1] == "vmap") {
|
||||
if table.Maps[name] == nil {
|
||||
return notFoundError("no such map %q", name)
|
||||
}
|
||||
} else if i > 0 && (words[i-1] == "offload" || words[i-1] == "add") {
|
||||
if table.Flowtables[name] == nil {
|
||||
return notFoundError("no such flowtable %q", name)
|
||||
}
|
||||
} else {
|
||||
// recent nft lets you use a map in a set lookup
|
||||
if table.Sets[name] == nil && table.Maps[name] == nil {
|
||||
|
|
@ -480,20 +687,32 @@ func checkElementRefs(element *Element, table *FakeTable) error {
|
|||
|
||||
// Dump dumps the current contents of fake, in a way that looks like an nft transaction.
|
||||
func (fake *Fake) Dump() string {
|
||||
if fake.Table == nil {
|
||||
return ""
|
||||
}
|
||||
fake.RLock()
|
||||
defer fake.RUnlock()
|
||||
|
||||
buf := &strings.Builder{}
|
||||
for _, family := range sortKeys(fake.Tables) {
|
||||
for _, tableName := range sortKeys(fake.Tables[family]) {
|
||||
fake.dumpTable(buf, fake.Tables[family][tableName])
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
table := fake.Table
|
||||
func (fake *Fake) dumpTable(buf *strings.Builder, table *FakeTable) {
|
||||
flowtables := sortKeys(table.Flowtables)
|
||||
chains := sortKeys(table.Chains)
|
||||
sets := sortKeys(table.Sets)
|
||||
maps := sortKeys(table.Maps)
|
||||
counters := sortKeys(table.Counters)
|
||||
|
||||
// Write out all of the object adds first.
|
||||
|
||||
table.writeOperation(addVerb, &fake.nftContext, buf)
|
||||
for _, fname := range flowtables {
|
||||
ft := table.Flowtables[fname]
|
||||
ft.writeOperation(addVerb, &fake.nftContext, buf)
|
||||
}
|
||||
for _, cname := range chains {
|
||||
ch := table.Chains[cname]
|
||||
ch.writeOperation(addVerb, &fake.nftContext, buf)
|
||||
|
|
@ -506,7 +725,10 @@ func (fake *Fake) Dump() string {
|
|||
m := table.Maps[mname]
|
||||
m.writeOperation(addVerb, &fake.nftContext, buf)
|
||||
}
|
||||
|
||||
for _, cname := range counters {
|
||||
m := table.Counters[cname]
|
||||
m.writeOperation(addVerb, &fake.nftContext, buf)
|
||||
}
|
||||
// Now write their contents.
|
||||
|
||||
for _, cname := range chains {
|
||||
|
|
@ -531,10 +753,10 @@ func (fake *Fake) Dump() string {
|
|||
element.writeOperation(addVerb, &fake.nftContext, buf)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
var commonRegexp = regexp.MustCompile(`add ([^ ]*) ([^ ]*) ([^ ]*)( (.*))?`)
|
||||
|
||||
// ParseDump can parse a dump for a given nft instance.
|
||||
// It expects fake's table name and family in all rules.
|
||||
// The best way to verify that everything important was properly parsed is to
|
||||
|
|
@ -550,7 +772,6 @@ func (fake *Fake) ParseDump(data string) (err error) {
|
|||
}
|
||||
}()
|
||||
tx := fake.NewTransaction()
|
||||
commonRegexp := regexp.MustCompile(fmt.Sprintf(`add %s %s %s (.*)`, noSpaceGroup, fake.family, fake.table))
|
||||
|
||||
for i, line = range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
|
@ -559,12 +780,33 @@ func (fake *Fake) ParseDump(data string) (err error) {
|
|||
}
|
||||
match := commonRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("could not parse, or wrong table/family")
|
||||
return fmt.Errorf("could not parse")
|
||||
}
|
||||
family := Family(match[2])
|
||||
table := match[3]
|
||||
|
||||
// If fake has a family and table specified then the parsed family and
|
||||
// table must match (but then we clear them, because we don't want them
|
||||
// to be added to the returned objects, for backward compatibility).
|
||||
if fake.family != "" {
|
||||
if family != fake.family {
|
||||
return fmt.Errorf("wrong family %q in rule", family)
|
||||
}
|
||||
family = ""
|
||||
}
|
||||
if fake.table != "" {
|
||||
if table != fake.table {
|
||||
return fmt.Errorf("wrong table name %q in rule", table)
|
||||
}
|
||||
table = ""
|
||||
}
|
||||
|
||||
var obj Object
|
||||
switch match[1] {
|
||||
case "table":
|
||||
obj = &Table{}
|
||||
case "flowtable":
|
||||
obj = &Flowtable{}
|
||||
case "chain":
|
||||
obj = &Chain{}
|
||||
case "rule":
|
||||
|
|
@ -575,10 +817,12 @@ func (fake *Fake) ParseDump(data string) (err error) {
|
|||
obj = &Set{}
|
||||
case "element":
|
||||
obj = &Element{}
|
||||
case "counter":
|
||||
obj = &Counter{}
|
||||
default:
|
||||
return fmt.Errorf("unknown object %s", match[1])
|
||||
}
|
||||
err = obj.parse(match[2])
|
||||
err = obj.parse(family, table, match[5])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -623,10 +867,17 @@ func (table *FakeTable) copy() *FakeTable {
|
|||
}
|
||||
|
||||
tcopy := &FakeTable{
|
||||
Table: table.Table,
|
||||
Chains: make(map[string]*FakeChain),
|
||||
Sets: make(map[string]*FakeSet),
|
||||
Maps: make(map[string]*FakeMap),
|
||||
Table: table.Table,
|
||||
Flowtables: make(map[string]*FakeFlowtable),
|
||||
Chains: make(map[string]*FakeChain),
|
||||
Sets: make(map[string]*FakeSet),
|
||||
Maps: make(map[string]*FakeMap),
|
||||
Counters: make(map[string]*FakeCounter),
|
||||
}
|
||||
for name, flowtable := range table.Flowtables {
|
||||
tcopy.Flowtables[name] = &FakeFlowtable{
|
||||
Flowtable: flowtable.Flowtable,
|
||||
}
|
||||
}
|
||||
for name, chain := range table.Chains {
|
||||
tcopy.Chains[name] = &FakeChain{
|
||||
|
|
@ -646,7 +897,9 @@ func (table *FakeTable) copy() *FakeTable {
|
|||
Elements: append([]*Element{}, mapObj.Elements...),
|
||||
}
|
||||
}
|
||||
|
||||
for name, counter := range table.Counters {
|
||||
tcopy.Counters[name] = counter
|
||||
}
|
||||
return tcopy
|
||||
}
|
||||
|
||||
|
|
@ -669,3 +922,12 @@ func (m *FakeMap) FindElement(key ...string) *Element {
|
|||
}
|
||||
return m.Elements[index]
|
||||
}
|
||||
|
||||
// ListCounters is part of Interface
|
||||
func (fake *Fake) ListCounters(_ context.Context) ([]*Counter, error) {
|
||||
counters := make([]*Counter, len(fake.Table.Counters))
|
||||
for _, fakeCounter := range fake.Table.Counters {
|
||||
counters = append(counters, PtrTo(fakeCounter.Counter))
|
||||
}
|
||||
return counters, nil
|
||||
}
|
||||
|
|
|
|||
328
vendor/sigs.k8s.io/knftables/nftables.go
generated
vendored
328
vendor/sigs.k8s.io/knftables/nftables.go
generated
vendored
|
|
@ -40,9 +40,14 @@ type Interface interface {
|
|||
// result.
|
||||
Check(ctx context.Context, tx *Transaction) error
|
||||
|
||||
// ListAll returns a map containing the names of all objects in the table,
|
||||
// grouped by object type. If there are no objects, this will return an empty list
|
||||
// and no error.
|
||||
ListAll(ctx context.Context) (map[string][]string, error)
|
||||
|
||||
// List returns a list of the names of the objects of objectType ("chain", "set",
|
||||
// or "map") in the table. If there are no such objects, this will return an empty
|
||||
// list and no error.
|
||||
// "map" or "counter") in the table. If there are no such objects, this will
|
||||
// return an empty list and no error.
|
||||
List(ctx context.Context, objectType string) ([]string, error)
|
||||
|
||||
// ListRules returns a list of the rules in a chain, in order. If no chain name is
|
||||
|
|
@ -58,8 +63,32 @@ type Interface interface {
|
|||
// be "set" or "map".) If the set/map exists but contains no elements, this will
|
||||
// return an empty list and no error.
|
||||
ListElements(ctx context.Context, objectType, name string) ([]*Element, error)
|
||||
|
||||
// ListCounters returns a list of the counters in the table.
|
||||
ListCounters(ctx context.Context) ([]*Counter, error)
|
||||
}
|
||||
|
||||
// Option is an optional nftables feature that an Interface might or might not support
|
||||
type Option string
|
||||
|
||||
const (
|
||||
// NoObjectCommentEmulation turns off the default knftables.Interface behavior of
|
||||
// ignoring comments on Table, Chain, Set, and Map objects if the underlying CLI
|
||||
// or kernel does not support them. (The only real reason to specify this is if
|
||||
// you want to avoid doing any "nft check" calls at construction time.)
|
||||
NoObjectCommentEmulation Option = "NoObjectCommentEmulation"
|
||||
|
||||
// RequireDestroy tells knftables.New to fail if the `nft destroy` command is not
|
||||
// available.
|
||||
RequireDestroy Option = "RequireDestroy"
|
||||
|
||||
// EmulateDestroy tells the Interface to emulate the `nft destroy` command if it
|
||||
// is not available. If you pass this option, then that will restrict the ways
|
||||
// that you can use the `tx.Destroy()` method to be compatible with destroy
|
||||
// emulation; see the docs for that method for more details.
|
||||
EmulateDestroy Option = "EmulateDestroy"
|
||||
)
|
||||
|
||||
type nftContext struct {
|
||||
family Family
|
||||
table string
|
||||
|
|
@ -67,6 +96,14 @@ type nftContext struct {
|
|||
// noObjectComments is true if comments on Table/Chain/Set/Map are not supported.
|
||||
// (Comments on Rule and Element are always supported.)
|
||||
noObjectComments bool
|
||||
|
||||
// emulateDestroy is true if tx.Destroy() should restrict itself to destroy
|
||||
// actions that are compatible with an emulated version of "nft destroy"
|
||||
emulateDestroy bool
|
||||
|
||||
// hasDestroy is true emulateDestroy is true but the nft binary actually supports
|
||||
// "destroy" so we don't need to bother emulating it.
|
||||
hasDestroy bool
|
||||
}
|
||||
|
||||
// realNFTables is an implementation of Interface
|
||||
|
|
@ -80,11 +117,24 @@ type realNFTables struct {
|
|||
path string
|
||||
}
|
||||
|
||||
func optionSet(options []Option, option Option) bool {
|
||||
for _, o := range options {
|
||||
if o == option {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// newInternal creates a new nftables.Interface for interacting with the given table; this
|
||||
// is split out from New() so it can be used from unit tests with a fakeExec.
|
||||
func newInternal(family Family, table string, execer execer) (Interface, error) {
|
||||
func newInternal(family Family, table string, execer execer, options ...Option) (Interface, error) {
|
||||
var err error
|
||||
|
||||
if (family == "") != (table == "") {
|
||||
return nil, fmt.Errorf("family and table must either both be specified or both be empty")
|
||||
}
|
||||
|
||||
nft := &realNFTables{
|
||||
nftContext: nftContext{
|
||||
family: family,
|
||||
|
|
@ -108,30 +158,82 @@ func newInternal(family Family, table string, execer execer) (Interface, error)
|
|||
return nil, fmt.Errorf("nft version must be v1.0.1 or later (got %s)", strings.TrimSpace(out))
|
||||
}
|
||||
|
||||
testFamily := family
|
||||
if testFamily == "" {
|
||||
testFamily = InetFamily
|
||||
}
|
||||
testTable := table
|
||||
if testTable == "" {
|
||||
testTable = "test"
|
||||
}
|
||||
|
||||
// Check that (a) nft works, (b) we have permission, (c) the kernel is new enough
|
||||
// to support object comments.
|
||||
tx := nft.NewTransaction()
|
||||
tx.Add(&Table{
|
||||
Family: testFamily,
|
||||
Name: testTable,
|
||||
Comment: PtrTo("test"),
|
||||
})
|
||||
if err := nft.Check(context.TODO(), tx); err != nil {
|
||||
// Try again, checking just that (a) nft works, (b) we have permission.
|
||||
tx := nft.NewTransaction()
|
||||
tx.Add(&Table{})
|
||||
if err := nft.Check(context.TODO(), tx); err != nil {
|
||||
nft.noObjectComments = true
|
||||
if !optionSet(options, NoObjectCommentEmulation) {
|
||||
// Try again, checking just that (a) nft works, (b) we have permission.
|
||||
tx := nft.NewTransaction()
|
||||
tx.Add(&Table{
|
||||
Family: testFamily,
|
||||
Name: testTable,
|
||||
})
|
||||
err = nft.Check(context.TODO(), tx)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not run nftables command: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
nft.noObjectComments = true
|
||||
requireDestroy := optionSet(options, RequireDestroy)
|
||||
emulateDestroy := optionSet(options, EmulateDestroy)
|
||||
if requireDestroy || emulateDestroy {
|
||||
// Check if "nft destroy" is available.
|
||||
tx = nft.NewTransaction()
|
||||
tx.Destroy(&Table{})
|
||||
if err := nft.Check(context.TODO(), tx); err != nil {
|
||||
if requireDestroy {
|
||||
return nil, fmt.Errorf("`nft destroy` is not available: %w", err)
|
||||
}
|
||||
} else {
|
||||
nft.hasDestroy = true
|
||||
}
|
||||
// Can't set this until after doing the test above
|
||||
nft.emulateDestroy = emulateDestroy
|
||||
}
|
||||
|
||||
return nft, nil
|
||||
}
|
||||
|
||||
// New creates a new nftables.Interface for interacting with the given table. If nftables
|
||||
// is not available/usable on the current host, it will return an error.
|
||||
func New(family Family, table string) (Interface, error) {
|
||||
return newInternal(family, table, realExec{})
|
||||
// New creates a new nftables.Interface. If nftables is not available/usable on the
|
||||
// current host, it will return an error.
|
||||
//
|
||||
// Normally, family and table will specify the family and table to use for all operations
|
||||
// on the returned Interface. However, if you leave them empty (`""`), then the Interface
|
||||
// will have no associated family/table and (a) you must explicitly fill in those fields
|
||||
// in any objects you use in a Transaction, (b) you can't use any of the List* methods.
|
||||
//
|
||||
// In addition to the family and table, you can specify additional comma-separated options
|
||||
// to New(). The currently-supported options are:
|
||||
//
|
||||
// - NoObjectCommentEmulation: disables the default knftables.Interface behavior of
|
||||
// ignoring comments on Table, Chain, Set, and Map objects if the underlying CLI or
|
||||
// kernel does not support them.
|
||||
//
|
||||
// - RequireDestroy: require the system to support `nft destroy`; the New() call will
|
||||
// fail with an error on older systems.
|
||||
//
|
||||
// - EmulateDestroy: adjust the API of `tx.Destroy()` to make it possible to emulate via
|
||||
// `nft add` and `nft delete` on systems that do not have `nft destroy`; see the docs
|
||||
// for `tx.Destroy()` for more details.
|
||||
func New(family Family, table string, options ...Option) (Interface, error) {
|
||||
return newInternal(family, table, realExec{}, options...)
|
||||
}
|
||||
|
||||
// NewTransaction is part of Interface
|
||||
|
|
@ -149,14 +251,11 @@ func (nft *realNFTables) Run(ctx context.Context, tx *Transaction) error {
|
|||
}
|
||||
|
||||
nft.buffer.Reset()
|
||||
err := tx.populateCommandBuf(nft.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.populateCommandBuf(nft.buffer)
|
||||
|
||||
cmd := exec.CommandContext(ctx, nft.path, "-f", "-")
|
||||
cmd.Stdin = nft.buffer
|
||||
_, err = nft.exec.Run(cmd)
|
||||
_, err := nft.exec.Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -170,14 +269,11 @@ func (nft *realNFTables) Check(ctx context.Context, tx *Transaction) error {
|
|||
}
|
||||
|
||||
nft.buffer.Reset()
|
||||
err := tx.populateCommandBuf(nft.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.populateCommandBuf(nft.buffer)
|
||||
|
||||
cmd := exec.CommandContext(ctx, nft.path, "--check", "-f", "-")
|
||||
cmd.Stdin = nft.buffer
|
||||
_, err = nft.exec.Run(cmd)
|
||||
_, err := nft.exec.Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -192,9 +288,9 @@ func jsonVal[T any](json map[string]interface{}, key string) (T, bool) {
|
|||
return zero, false
|
||||
}
|
||||
|
||||
// getJSONObjects takes the output of "nft -j list", validates it, and returns an array
|
||||
// of just the objects of objectType.
|
||||
func getJSONObjects(listOutput, objectType string) ([]map[string]interface{}, error) {
|
||||
// parseJSONResult takes the output of "nft -j list", validates it, and returns the array
|
||||
// of objects (including the "metainfo" object)
|
||||
func parseJSONObjects(listOutput string) ([]map[string]map[string]interface{}, error) {
|
||||
// listOutput should contain JSON looking like:
|
||||
//
|
||||
// {
|
||||
|
|
@ -225,23 +321,7 @@ func getJSONObjects(listOutput, objectType string) ([]map[string]interface{}, er
|
|||
// ]
|
||||
// }
|
||||
//
|
||||
// In this case, given objectType "chain", we would return
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "family": "ip",
|
||||
// "table": "kube-proxy",
|
||||
// "name": "KUBE-SERVICES",
|
||||
// "handle": 3
|
||||
// },
|
||||
// {
|
||||
// "family": "ip",
|
||||
// "table": "kube-proxy",
|
||||
// "name": "KUBE-NODEPORTS",
|
||||
// "handle": 4
|
||||
// },
|
||||
// ...
|
||||
// ]
|
||||
// parseJSONResult returns the array of objects tagged "nftables".
|
||||
|
||||
jsonResult := map[string][]map[string]map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(listOutput), &jsonResult); err != nil {
|
||||
|
|
@ -261,6 +341,35 @@ func getJSONObjects(listOutput, objectType string) ([]map[string]interface{}, er
|
|||
if version, ok := jsonVal[float64](metainfo, "json_schema_version"); !ok || version != 1.0 {
|
||||
return nil, fmt.Errorf("could not find supported json_schema_version in nft output %q", listOutput)
|
||||
}
|
||||
return nftablesResult, nil
|
||||
}
|
||||
|
||||
// getJSONObjects takes the output of "nft -j list", validates it, and returns an array
|
||||
// of just the objects of objectType.
|
||||
func getJSONObjects(listOutput, objectType string) ([]map[string]interface{}, error) {
|
||||
// Given the result from the parseJSONObjects example above, and objectType
|
||||
// "chain", we would return
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "family": "ip",
|
||||
// "table": "kube-proxy",
|
||||
// "name": "KUBE-SERVICES",
|
||||
// "handle": 3
|
||||
// },
|
||||
// {
|
||||
// "family": "ip",
|
||||
// "table": "kube-proxy",
|
||||
// "name": "KUBE-NODEPORTS",
|
||||
// "handle": 4
|
||||
// },
|
||||
// ...
|
||||
// ]
|
||||
|
||||
nftablesResult, err := parseJSONObjects(listOutput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var objects []map[string]interface{}
|
||||
for _, objContainer := range nftablesResult {
|
||||
|
|
@ -272,37 +381,65 @@ func getJSONObjects(listOutput, objectType string) ([]map[string]interface{}, er
|
|||
return objects, nil
|
||||
}
|
||||
|
||||
// List is part of Interface.
|
||||
func (nft *realNFTables) List(ctx context.Context, objectType string) ([]string, error) {
|
||||
// All currently-existing nftables object types have plural forms that are just
|
||||
// the singular form plus 's'.
|
||||
var typeSingular, typePlural string
|
||||
if objectType[len(objectType)-1] == 's' {
|
||||
typeSingular = objectType[:len(objectType)-1]
|
||||
typePlural = objectType
|
||||
} else {
|
||||
typeSingular = objectType
|
||||
typePlural = objectType + "s"
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, nft.path, "--json", "list", typePlural, string(nft.family))
|
||||
// ListAll is part of Interface.
|
||||
func (nft *realNFTables) ListAll(ctx context.Context) (map[string][]string, error) {
|
||||
cmd := exec.CommandContext(ctx, nft.path, "--json", "list", "table", string(nft.family), nft.table)
|
||||
out, err := nft.exec.Run(cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run nft: %w", err)
|
||||
}
|
||||
|
||||
objects, err := getJSONObjects(out, typeSingular)
|
||||
nftablesResult, err := parseJSONObjects(out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string][]string)
|
||||
for i, objContainer := range nftablesResult {
|
||||
if i == 0 {
|
||||
// Skip "metainfo"
|
||||
continue
|
||||
}
|
||||
for objectType, obj := range objContainer {
|
||||
if name, ok := jsonVal[string](obj, "name"); ok {
|
||||
result[objectType] = append(result[objectType], name)
|
||||
}
|
||||
// Shouldn't be more than one field in objContainer, but ignore it
|
||||
// if there is.
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// List is part of Interface.
|
||||
func (nft *realNFTables) List(ctx context.Context, objectType string) ([]string, error) {
|
||||
if nft.table == "" {
|
||||
return nil, fmt.Errorf("can't use List() on a knftables.Interface with no associated family/table")
|
||||
}
|
||||
|
||||
// objectType is allowed to be either singular or plural. All currently-existing
|
||||
// nftables object types have plural forms that are just the singular form plus 's',
|
||||
// and none have singular forms ending in 's'.
|
||||
if objectType[len(objectType)-1] == 's' {
|
||||
objectType = objectType[:len(objectType)-1]
|
||||
}
|
||||
|
||||
// We want to restrict nft to looking only at our table, so we have to do "list table"
|
||||
// rather than any variant of "list <objectType>".
|
||||
cmd := exec.CommandContext(ctx, nft.path, "--json", "list", "table", string(nft.family), nft.table)
|
||||
out, err := nft.exec.Run(cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run nft: %w", err)
|
||||
}
|
||||
|
||||
objects, err := getJSONObjects(out, objectType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []string
|
||||
for _, obj := range objects {
|
||||
objTable, _ := jsonVal[string](obj, "table")
|
||||
if objTable != nft.table {
|
||||
continue
|
||||
}
|
||||
|
||||
if name, ok := jsonVal[string](obj, "name"); ok {
|
||||
result = append(result, name)
|
||||
}
|
||||
|
|
@ -312,7 +449,10 @@ func (nft *realNFTables) List(ctx context.Context, objectType string) ([]string,
|
|||
|
||||
// ListRules is part of Interface
|
||||
func (nft *realNFTables) ListRules(ctx context.Context, chain string) ([]*Rule, error) {
|
||||
// If no chain is given, return all rules from within the table.
|
||||
if nft.table == "" {
|
||||
return nil, fmt.Errorf("can't use ListRules() on a knftables.Interface with no associated family/table")
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if chain == "" {
|
||||
cmd = exec.CommandContext(ctx, nft.path, "--json", "list", "table", string(nft.family), nft.table)
|
||||
|
|
@ -358,6 +498,10 @@ func (nft *realNFTables) ListRules(ctx context.Context, chain string) ([]*Rule,
|
|||
|
||||
// ListElements is part of Interface
|
||||
func (nft *realNFTables) ListElements(ctx context.Context, objectType, name string) ([]*Element, error) {
|
||||
if nft.table == "" {
|
||||
return nil, fmt.Errorf("can't use ListElements() on a knftables.Interface with no associated family/table")
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, nft.path, "--json", "list", objectType, string(nft.family), nft.table, name)
|
||||
out, err := nft.exec.Run(cmd)
|
||||
if err != nil {
|
||||
|
|
@ -390,12 +534,14 @@ func (nft *realNFTables) ListElements(ctx context.Context, objectType, name stri
|
|||
key, value = tuple[0], tuple[1]
|
||||
}
|
||||
|
||||
// If the element has a comment, then key will be a compound object like:
|
||||
// If the element has a comment or a counter, then key will be a compound
|
||||
// object like:
|
||||
//
|
||||
// {
|
||||
// "elem": {
|
||||
// "val": "192.168.0.1",
|
||||
// "comment": "this is a comment"
|
||||
// "comment": "this is a comment",
|
||||
// "counter": { "packets": 0, "bytes": 0 }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
|
@ -473,15 +619,13 @@ func parseElementValue(json interface{}) ([]string, error) {
|
|||
return []string{fmt.Sprintf("%d", int(val))}, nil
|
||||
case map[string]interface{}:
|
||||
if concat, _ := jsonVal[[]interface{}](val, "concat"); concat != nil {
|
||||
vals := make([]string, len(concat))
|
||||
vals := make([]string, 0, len(concat))
|
||||
for i := range concat {
|
||||
if str, ok := concat[i].(string); ok {
|
||||
vals[i] = str
|
||||
} else if num, ok := concat[i].(float64); ok {
|
||||
vals[i] = fmt.Sprintf("%d", int(num))
|
||||
} else {
|
||||
return nil, fmt.Errorf("could not parse element value %q", concat[i])
|
||||
newVals, err := parseElementValue(concat[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals = append(vals, newVals...)
|
||||
}
|
||||
return vals, nil
|
||||
} else if prefix, _ := jsonVal[map[string]interface{}](val, "prefix"); prefix != nil {
|
||||
|
|
@ -512,3 +656,43 @@ func parseElementValue(json interface{}) ([]string, error) {
|
|||
|
||||
return nil, fmt.Errorf("could not parse element value %q", json)
|
||||
}
|
||||
|
||||
// ListCounters is part of Interface
|
||||
func (nft *realNFTables) ListCounters(ctx context.Context) ([]*Counter, error) {
|
||||
if nft.table == "" {
|
||||
return nil, fmt.Errorf("can't use ListCounters() on a knftables.Interface with no associated family/table")
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, nft.path, "--json", "list", "counters", "table", string(nft.family), nft.table)
|
||||
out, err := nft.exec.Run(cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run nft: %w", err)
|
||||
}
|
||||
|
||||
objects, err := getJSONObjects(out, "counter")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objectToCounter := func(object map[string]interface{}) *Counter {
|
||||
counter := &Counter{
|
||||
Name: object["name"].(string),
|
||||
Packets: PtrTo(uint64(object["packets"].(float64))),
|
||||
Bytes: PtrTo(uint64(object["bytes"].(float64))),
|
||||
}
|
||||
if handle, ok := jsonVal[string](object, "comment"); ok {
|
||||
counter.Comment = PtrTo(handle)
|
||||
}
|
||||
if handle, ok := jsonVal[float64](object, "handle"); ok {
|
||||
counter.Handle = PtrTo(int(handle))
|
||||
}
|
||||
|
||||
return counter
|
||||
}
|
||||
|
||||
counters := make([]*Counter, 0, len(objects))
|
||||
for _, object := range objects {
|
||||
counters = append(counters, objectToCounter(object))
|
||||
}
|
||||
return counters, nil
|
||||
}
|
||||
|
|
|
|||
359
vendor/sigs.k8s.io/knftables/objects.go
generated
vendored
359
vendor/sigs.k8s.io/knftables/objects.go
generated
vendored
|
|
@ -46,18 +46,43 @@ func getComment(commentGroup string) *string {
|
|||
return &noQuotes
|
||||
}
|
||||
|
||||
func getTable(ctx *nftContext, family Family, table string) (Family, string, error) {
|
||||
switch {
|
||||
case ctx.family == "" && family == "":
|
||||
return "", "", fmt.Errorf("must specify family and table for each object when the Interface has no default")
|
||||
case ctx.family != "" && family != "" && family != ctx.family:
|
||||
return "", "", fmt.Errorf("cannot override family or table when the Interface has a default")
|
||||
case ctx.family != "" && family == "":
|
||||
family = ctx.family
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.table == "" && table == "":
|
||||
return "", "", fmt.Errorf("must specify family and table for each object when the Interface has no default")
|
||||
case ctx.table != "" && table != "" && table != ctx.table:
|
||||
return "", "", fmt.Errorf("cannot override family or table when the Interface has a default")
|
||||
case ctx.table != "" && table == "":
|
||||
table = ctx.table
|
||||
}
|
||||
|
||||
return family, table, nil
|
||||
}
|
||||
|
||||
var commentGroup = `(".*")`
|
||||
var noSpaceGroup = `([^ ]*)`
|
||||
var numberGroup = `([0-9]*)`
|
||||
|
||||
// Object implementation for Table
|
||||
func (table *Table) validate(verb verb) error {
|
||||
func (table *Table) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, table.Family, table.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
switch verb {
|
||||
case addVerb, createVerb, flushVerb:
|
||||
if table.Handle != nil {
|
||||
return fmt.Errorf("cannot specify Handle in %s operation", verb)
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
// Handle can be nil or non-nil
|
||||
default:
|
||||
return fmt.Errorf("%s is not implemented for tables", verb)
|
||||
|
|
@ -67,40 +92,76 @@ func (table *Table) validate(verb verb) error {
|
|||
}
|
||||
|
||||
func (table *Table) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
family, tableName, _ := getTable(ctx, table.Family, table.Name)
|
||||
|
||||
// Special case for delete-by-handle
|
||||
if verb == deleteVerb && table.Handle != nil {
|
||||
fmt.Fprintf(writer, "delete table %s handle %d", ctx.family, *table.Handle)
|
||||
if (verb == deleteVerb || verb == destroyVerb) && table.Handle != nil {
|
||||
fmt.Fprintf(writer, "%s table %s handle %d", verb, family, *table.Handle)
|
||||
return
|
||||
}
|
||||
|
||||
// All other cases refer to the table by name
|
||||
fmt.Fprintf(writer, "%s table %s %s", verb, ctx.family, ctx.table)
|
||||
fmt.Fprintf(writer, "%s table %s %s", verb, family, tableName)
|
||||
if verb == addVerb || verb == createVerb {
|
||||
if table.Comment != nil && !ctx.noObjectComments {
|
||||
fmt.Fprintf(writer, " { comment %q ; }", *table.Comment)
|
||||
hasComment := table.Comment != nil && !ctx.noObjectComments
|
||||
if hasComment || len(table.Flags) != 0 {
|
||||
fmt.Fprintf(writer, " {")
|
||||
if hasComment {
|
||||
fmt.Fprintf(writer, " comment %q ;", *table.Comment)
|
||||
}
|
||||
if len(table.Flags) != 0 {
|
||||
fmt.Fprintf(writer, " flags ")
|
||||
for i := range table.Flags {
|
||||
if i > 0 {
|
||||
fmt.Fprintf(writer, ",")
|
||||
}
|
||||
fmt.Fprintf(writer, "%s", table.Flags[i])
|
||||
}
|
||||
fmt.Fprintf(writer, " ;")
|
||||
}
|
||||
fmt.Fprintf(writer, " }")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(writer, "\n")
|
||||
}
|
||||
|
||||
var tableRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
`(?:{ comment %s ; })?`, commentGroup))
|
||||
`(?:{ (?:comment %s ; )?(?:flags %s ; )?})?`, commentGroup, noSpaceGroup))
|
||||
|
||||
func (table *Table) parse(line string) error {
|
||||
func parseTableFlags(s string) []TableFlag {
|
||||
var res []TableFlag
|
||||
for _, flag := range strings.Split(s, ",") {
|
||||
res = append(res, TableFlag(flag))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (table *Table) parse(family Family, tableName, line string) error {
|
||||
match := tableRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("failed parsing table add command")
|
||||
}
|
||||
table.Family = family
|
||||
table.Name = tableName
|
||||
table.Comment = getComment(match[1])
|
||||
if match[2] != "" {
|
||||
table.Flags = parseTableFlags(match[2])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object implementation for Chain
|
||||
func (chain *Chain) validate(verb verb) error {
|
||||
func (chain *Chain) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, chain.Family, chain.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
if chain.Hook == nil {
|
||||
if chain.Type != nil || chain.Priority != nil {
|
||||
return fmt.Errorf("regular chain %q must not specify Type or Priority", chain.Name)
|
||||
}
|
||||
if chain.Policy != nil {
|
||||
return fmt.Errorf("regular chain %q must not specify Policy", chain.Name)
|
||||
}
|
||||
if chain.Device != nil {
|
||||
return fmt.Errorf("regular chain %q must not specify Device", chain.Name)
|
||||
}
|
||||
|
|
@ -118,7 +179,7 @@ func (chain *Chain) validate(verb verb) error {
|
|||
if chain.Handle != nil {
|
||||
return fmt.Errorf("cannot specify Handle in %s operation", verb)
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
if chain.Name == "" && chain.Handle == nil {
|
||||
return fmt.Errorf("must specify either name or handle")
|
||||
}
|
||||
|
|
@ -130,13 +191,15 @@ func (chain *Chain) validate(verb verb) error {
|
|||
}
|
||||
|
||||
func (chain *Chain) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
family, table, _ := getTable(ctx, chain.Family, chain.Table)
|
||||
|
||||
// Special case for delete-by-handle
|
||||
if verb == deleteVerb && chain.Handle != nil {
|
||||
fmt.Fprintf(writer, "delete chain %s %s handle %d", ctx.family, ctx.table, *chain.Handle)
|
||||
if (verb == deleteVerb || verb == destroyVerb) && chain.Handle != nil {
|
||||
fmt.Fprintf(writer, "%s chain %s %s handle %d", verb, family, table, *chain.Handle)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "%s chain %s %s %s", verb, ctx.family, ctx.table, chain.Name)
|
||||
fmt.Fprintf(writer, "%s chain %s %s %s", verb, family, table, chain.Name)
|
||||
if verb == addVerb || verb == createVerb {
|
||||
if chain.Type != nil || (chain.Comment != nil && !ctx.noObjectComments) {
|
||||
fmt.Fprintf(writer, " {")
|
||||
|
|
@ -151,11 +214,14 @@ func (chain *Chain) writeOperation(verb verb, ctx *nftContext, writer io.Writer)
|
|||
// versions of nft don't accept certain named priorities
|
||||
// in all contexts (eg, "dstnat" priority in the "output"
|
||||
// hook).
|
||||
if priority, err := ParsePriority(ctx.family, string(*chain.Priority)); err == nil {
|
||||
if priority, err := ParsePriority(family, string(*chain.Priority)); err == nil {
|
||||
fmt.Fprintf(writer, " priority %d ;", priority)
|
||||
} else {
|
||||
fmt.Fprintf(writer, " priority %s ;", *chain.Priority)
|
||||
}
|
||||
if chain.Policy != nil {
|
||||
fmt.Fprintf(writer, " policy %s ;", *chain.Policy)
|
||||
}
|
||||
}
|
||||
if chain.Comment != nil && !ctx.noObjectComments {
|
||||
fmt.Fprintf(writer, " comment %q ;", *chain.Comment)
|
||||
|
|
@ -168,18 +234,20 @@ func (chain *Chain) writeOperation(verb verb, ctx *nftContext, writer io.Writer)
|
|||
fmt.Fprintf(writer, "\n")
|
||||
}
|
||||
|
||||
// groups in []: [1]%s(?: {(?: type [2]%s hook [3]%s(?: device "[4]%s")(?: priority [5]%s ;))(?: comment [6]%s ;) })
|
||||
// groups in []: [1]%s(?: {(?: type [2]%s hook [3]%s(?: device "[4]%s")(?: priority [5]%s ;)(?: policy [6]%s ;)?)(?: comment [7]%s ;) })
|
||||
var chainRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
`%s(?: {(?: type %s hook %s(?: device "%s")?(?: priority %s ;))?(?: comment %s ;)? })?`,
|
||||
noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, commentGroup))
|
||||
`%s(?: {(?: type %s hook %s(?: device "%s")?(?: priority %s ;)(?: policy %s ;)?)?(?: comment %s ;)? })?`,
|
||||
noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, commentGroup))
|
||||
|
||||
func (chain *Chain) parse(line string) error {
|
||||
func (chain *Chain) parse(family Family, table, line string) error {
|
||||
match := chainRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("failed parsing chain add command")
|
||||
}
|
||||
chain.Family = family
|
||||
chain.Table = table
|
||||
chain.Name = match[1]
|
||||
chain.Comment = getComment(match[6])
|
||||
chain.Comment = getComment(match[7])
|
||||
if match[2] != "" {
|
||||
chain.Type = (*BaseChainType)(&match[2])
|
||||
}
|
||||
|
|
@ -192,11 +260,17 @@ func (chain *Chain) parse(line string) error {
|
|||
if match[5] != "" {
|
||||
chain.Priority = (*BaseChainPriority)(&match[5])
|
||||
}
|
||||
if match[6] != "" {
|
||||
chain.Policy = (*BaseChainPolicy)(&match[6])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object implementation for Rule
|
||||
func (rule *Rule) validate(verb verb) error {
|
||||
func (rule *Rule) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, rule.Family, rule.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
if rule.Chain == "" {
|
||||
return fmt.Errorf("no chain name specified for rule")
|
||||
}
|
||||
|
|
@ -217,7 +291,7 @@ func (rule *Rule) validate(verb verb) error {
|
|||
if rule.Handle == nil {
|
||||
return fmt.Errorf("must specify Handle with %s", verb)
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
if rule.Handle == nil {
|
||||
return fmt.Errorf("must specify Handle with %s", verb)
|
||||
}
|
||||
|
|
@ -229,7 +303,9 @@ func (rule *Rule) validate(verb verb) error {
|
|||
}
|
||||
|
||||
func (rule *Rule) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
fmt.Fprintf(writer, "%s rule %s %s %s", verb, ctx.family, ctx.table, rule.Chain)
|
||||
family, table, _ := getTable(ctx, rule.Family, rule.Table)
|
||||
|
||||
fmt.Fprintf(writer, "%s rule %s %s %s", verb, family, table, rule.Chain)
|
||||
if rule.Index != nil {
|
||||
fmt.Fprintf(writer, " index %d", *rule.Index)
|
||||
} else if rule.Handle != nil {
|
||||
|
|
@ -253,11 +329,13 @@ var ruleRegexp = regexp.MustCompile(fmt.Sprintf(
|
|||
`%s(?: index %s)?(?: handle %s)? ([^"]*)(?: comment %s)?$`,
|
||||
noSpaceGroup, numberGroup, numberGroup, commentGroup))
|
||||
|
||||
func (rule *Rule) parse(line string) error {
|
||||
func (rule *Rule) parse(family Family, table, line string) error {
|
||||
match := ruleRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("failed parsing rule add command")
|
||||
}
|
||||
rule.Family = family
|
||||
rule.Table = table
|
||||
rule.Chain = match[1]
|
||||
rule.Rule = match[4]
|
||||
rule.Comment = getComment(match[5])
|
||||
|
|
@ -271,21 +349,26 @@ func (rule *Rule) parse(line string) error {
|
|||
}
|
||||
|
||||
// Object implementation for Set
|
||||
func (set *Set) validate(verb verb) error {
|
||||
func (set *Set) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, set.Family, set.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
switch verb {
|
||||
case addVerb, createVerb:
|
||||
if set.Name == "" {
|
||||
return fmt.Errorf("no name specified for set")
|
||||
}
|
||||
if (set.Type == "" && set.TypeOf == "") || (set.Type != "" && set.TypeOf != "") {
|
||||
return fmt.Errorf("set must specify either Type or TypeOf")
|
||||
}
|
||||
if set.Handle != nil {
|
||||
return fmt.Errorf("cannot specify Handle in %s operation", verb)
|
||||
}
|
||||
fallthrough
|
||||
case flushVerb:
|
||||
if set.Name == "" {
|
||||
return fmt.Errorf("no name specified for set")
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
if set.Name == "" && set.Handle == nil {
|
||||
return fmt.Errorf("must specify either name or handle")
|
||||
}
|
||||
|
|
@ -297,13 +380,15 @@ func (set *Set) validate(verb verb) error {
|
|||
}
|
||||
|
||||
func (set *Set) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
family, table, _ := getTable(ctx, set.Family, set.Table)
|
||||
|
||||
// Special case for delete-by-handle
|
||||
if verb == deleteVerb && set.Handle != nil {
|
||||
fmt.Fprintf(writer, "delete set %s %s handle %d", ctx.family, ctx.table, *set.Handle)
|
||||
if (verb == deleteVerb || verb == destroyVerb) && set.Handle != nil {
|
||||
fmt.Fprintf(writer, "%v set %s %s handle %d", verb, family, table, *set.Handle)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "%s set %s %s %s", verb, ctx.family, ctx.table, set.Name)
|
||||
fmt.Fprintf(writer, "%s set %s %s %s", verb, family, table, set.Name)
|
||||
if verb == addVerb || verb == createVerb {
|
||||
fmt.Fprintf(writer, " {")
|
||||
|
||||
|
|
@ -350,32 +435,39 @@ func (set *Set) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
|||
fmt.Fprintf(writer, "\n")
|
||||
}
|
||||
|
||||
func (set *Set) parse(line string) error {
|
||||
func (set *Set) parse(family Family, table, line string) error {
|
||||
match := setRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("failed parsing set add command")
|
||||
}
|
||||
set.Family = family
|
||||
set.Table = table
|
||||
set.Name, set.Type, set.TypeOf, set.Flags, set.Timeout, set.GCInterval,
|
||||
set.Size, set.Policy, set.Comment, set.AutoMerge = parseMapAndSetProps(match)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object implementation for Map
|
||||
func (mapObj *Map) validate(verb verb) error {
|
||||
func (mapObj *Map) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, mapObj.Family, mapObj.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
switch verb {
|
||||
case addVerb, createVerb:
|
||||
if mapObj.Name == "" {
|
||||
return fmt.Errorf("no name specified for map")
|
||||
}
|
||||
if (mapObj.Type == "" && mapObj.TypeOf == "") || (mapObj.Type != "" && mapObj.TypeOf != "") {
|
||||
return fmt.Errorf("map must specify either Type or TypeOf")
|
||||
}
|
||||
if mapObj.Handle != nil {
|
||||
return fmt.Errorf("cannot specify Handle in %s operation", verb)
|
||||
}
|
||||
fallthrough
|
||||
case flushVerb:
|
||||
if mapObj.Name == "" {
|
||||
return fmt.Errorf("no name specified for map")
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
if mapObj.Name == "" && mapObj.Handle == nil {
|
||||
return fmt.Errorf("must specify either name or handle")
|
||||
}
|
||||
|
|
@ -387,13 +479,15 @@ func (mapObj *Map) validate(verb verb) error {
|
|||
}
|
||||
|
||||
func (mapObj *Map) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
family, table, _ := getTable(ctx, mapObj.Family, mapObj.Table)
|
||||
|
||||
// Special case for delete-by-handle
|
||||
if verb == deleteVerb && mapObj.Handle != nil {
|
||||
fmt.Fprintf(writer, "delete map %s %s handle %d", ctx.family, ctx.table, *mapObj.Handle)
|
||||
if (verb == deleteVerb || verb == destroyVerb) && mapObj.Handle != nil {
|
||||
fmt.Fprintf(writer, "%v map %s %s handle %d", verb, family, table, *mapObj.Handle)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "%s map %s %s %s", verb, ctx.family, ctx.table, mapObj.Name)
|
||||
fmt.Fprintf(writer, "%s map %s %s %s", verb, family, table, mapObj.Name)
|
||||
if verb == addVerb || verb == createVerb {
|
||||
fmt.Fprintf(writer, " {")
|
||||
|
||||
|
|
@ -437,11 +531,13 @@ func (mapObj *Map) writeOperation(verb verb, ctx *nftContext, writer io.Writer)
|
|||
fmt.Fprintf(writer, "\n")
|
||||
}
|
||||
|
||||
func (mapObj *Map) parse(line string) error {
|
||||
func (mapObj *Map) parse(family Family, table, line string) error {
|
||||
match := mapRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("failed parsing map add command")
|
||||
}
|
||||
mapObj.Family = family
|
||||
mapObj.Table = table
|
||||
mapObj.Name, mapObj.Type, mapObj.TypeOf, mapObj.Flags, mapObj.Timeout, mapObj.GCInterval,
|
||||
mapObj.Size, mapObj.Policy, mapObj.Comment, _ = parseMapAndSetProps(match)
|
||||
return nil
|
||||
|
|
@ -500,7 +596,10 @@ func parseSetFlags(s string) []SetFlag {
|
|||
}
|
||||
|
||||
// Object implementation for Element
|
||||
func (element *Element) validate(verb verb) error {
|
||||
func (element *Element) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, element.Family, element.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
if element.Map == "" && element.Set == "" {
|
||||
return fmt.Errorf("no set/map name specified for element")
|
||||
} else if element.Set != "" && element.Map != "" {
|
||||
|
|
@ -519,7 +618,7 @@ func (element *Element) validate(verb verb) error {
|
|||
if element.Map != "" && len(element.Value) == 0 {
|
||||
return fmt.Errorf("no map value specified for map element")
|
||||
}
|
||||
case deleteVerb:
|
||||
case deleteVerb, destroyVerb:
|
||||
default:
|
||||
return fmt.Errorf("%s is not implemented for elements", verb)
|
||||
}
|
||||
|
|
@ -528,12 +627,14 @@ func (element *Element) validate(verb verb) error {
|
|||
}
|
||||
|
||||
func (element *Element) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
family, table, _ := getTable(ctx, element.Family, element.Table)
|
||||
|
||||
name := element.Set
|
||||
if name == "" {
|
||||
name = element.Map
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "%s element %s %s %s { %s", verb, ctx.family, ctx.table, name,
|
||||
fmt.Fprintf(writer, "%s element %s %s %s { %s", verb, family, table, name,
|
||||
strings.Join(element.Key, " . "))
|
||||
|
||||
if verb == addVerb || verb == createVerb {
|
||||
|
|
@ -557,7 +658,7 @@ var mapElementRegexp = regexp.MustCompile(fmt.Sprintf(
|
|||
var setElementRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
`%s { ([^"]*)(?: comment %s)? }`, noSpaceGroup, commentGroup))
|
||||
|
||||
func (element *Element) parse(line string) error {
|
||||
func (element *Element) parse(family Family, table, line string) error {
|
||||
// try to match map element first, since it has more groups, and if it matches, then we can be sure
|
||||
// this is map element.
|
||||
match := mapElementRegexp.FindStringSubmatch(line)
|
||||
|
|
@ -567,6 +668,8 @@ func (element *Element) parse(line string) error {
|
|||
return fmt.Errorf("failed parsing element add command")
|
||||
}
|
||||
}
|
||||
element.Family = family
|
||||
element.Table = table
|
||||
element.Comment = getComment(match[3])
|
||||
mapOrSetName := match[1]
|
||||
element.Key = append(element.Key, strings.Split(match[2], " . ")...)
|
||||
|
|
@ -579,3 +682,175 @@ func (element *Element) parse(line string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object implementation for Flowtable
|
||||
func (flowtable *Flowtable) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, flowtable.Family, flowtable.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
switch verb {
|
||||
case addVerb, createVerb:
|
||||
if flowtable.Name == "" {
|
||||
return fmt.Errorf("no name specified for flowtable")
|
||||
}
|
||||
if flowtable.Handle != nil {
|
||||
return fmt.Errorf("cannot specify Handle in %s operation", verb)
|
||||
}
|
||||
case deleteVerb, destroyVerb:
|
||||
if flowtable.Name == "" && flowtable.Handle == nil {
|
||||
return fmt.Errorf("must specify either name or handle")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%s is not implemented for flowtables", verb)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flowtable *Flowtable) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
family, table, _ := getTable(ctx, flowtable.Family, flowtable.Table)
|
||||
|
||||
// Special case for delete-by-handle
|
||||
if (verb == deleteVerb || verb == destroyVerb) && flowtable.Handle != nil {
|
||||
fmt.Fprintf(writer, "delete flowtable %s %s handle %d", family, table, *flowtable.Handle)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "%s flowtable %s %s %s", verb, family, table, flowtable.Name)
|
||||
if verb == addVerb || verb == createVerb {
|
||||
fmt.Fprintf(writer, " {")
|
||||
|
||||
if flowtable.Priority != nil {
|
||||
// since there is only one priority value allowed "filter" just use the value
|
||||
// provided and not try to parse it.
|
||||
fmt.Fprintf(writer, " hook ingress priority %s ;", *flowtable.Priority)
|
||||
}
|
||||
|
||||
if len(flowtable.Devices) > 0 {
|
||||
fmt.Fprintf(writer, " devices = { %s } ;", strings.Join(flowtable.Devices, ", "))
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, " }")
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "\n")
|
||||
}
|
||||
|
||||
// nft add flowtable inet example_table example_flowtable { hook ingress priority filter ; devices = { eth0 }; }
|
||||
var flowtableRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
`%s(?: {(?: hook ingress priority %s ;)(?: devices = {(.*)} ;) })?`,
|
||||
noSpaceGroup, noSpaceGroup))
|
||||
|
||||
func (flowtable *Flowtable) parse(family Family, table, line string) error {
|
||||
match := flowtableRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("failed parsing flowtableRegexp add command")
|
||||
}
|
||||
flowtable.Family = family
|
||||
flowtable.Table = table
|
||||
flowtable.Name = match[1]
|
||||
if match[2] != "" {
|
||||
flowtable.Priority = (*FlowtableIngressPriority)(&match[2])
|
||||
}
|
||||
// to avoid complex regular expressions the regex match everything between the brackets
|
||||
// to match a single interface or a comma separated list of interfaces, and it is postprocessed
|
||||
// here to remove the whitespaces.
|
||||
if match[3] != "" {
|
||||
devices := strings.Split(strings.TrimSpace(match[3]), ",")
|
||||
for i := range devices {
|
||||
devices[i] = strings.TrimSpace(devices[i])
|
||||
}
|
||||
if len(devices) > 0 {
|
||||
flowtable.Devices = devices
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nft add counter [family] table name [{ [ packets packets bytes bytes ; ] [ comment comment ; }]
|
||||
// ([^ ]*)(?: {(?: packets ([0-9]*) bytes ([0-9]*) ;)?(?: comment (".*") ;)? })?
|
||||
var counterRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
`%s(?: {(?: packets %s bytes %s ;)?(?: comment %s ;)? })?`,
|
||||
noSpaceGroup, numberGroup, numberGroup, commentGroup))
|
||||
|
||||
func (counter *Counter) parse(family Family, table, line string) error {
|
||||
match := counterRegexp.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
return fmt.Errorf("failed parsing table add command")
|
||||
}
|
||||
counter.Family = family
|
||||
counter.Table = table
|
||||
counter.Name = match[1]
|
||||
if match[2] != "" {
|
||||
counter.Packets = PtrTo(uint64(*parseInt(match[2])))
|
||||
}
|
||||
if match[3] != "" {
|
||||
counter.Bytes = PtrTo(uint64(*parseInt(match[3])))
|
||||
}
|
||||
if match[4] != "" {
|
||||
counter.Comment = getComment(match[4])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object implementation for Counter
|
||||
func (counter *Counter) validate(verb verb, ctx *nftContext) error {
|
||||
if _, _, err := getTable(ctx, counter.Family, counter.Table); err != nil {
|
||||
return err
|
||||
}
|
||||
switch verb {
|
||||
case addVerb, createVerb:
|
||||
if counter.Name == "" {
|
||||
return fmt.Errorf("no counter name specified")
|
||||
}
|
||||
if counter.Handle != nil {
|
||||
return fmt.Errorf("cannot specify Handle in %s operation", verb)
|
||||
}
|
||||
if counter.Packets != nil && counter.Bytes == nil {
|
||||
return fmt.Errorf("cannot specify Packets without Bytes in %s operation", verb)
|
||||
}
|
||||
if counter.Packets == nil && counter.Bytes != nil {
|
||||
return fmt.Errorf("cannot specify Bytes without Packets in %s operation", verb)
|
||||
}
|
||||
case deleteVerb, destroyVerb:
|
||||
if counter.Name == "" && counter.Handle == nil {
|
||||
return fmt.Errorf("neither counter name nor handle specified")
|
||||
}
|
||||
case resetVerb:
|
||||
if counter.Name == "" {
|
||||
return fmt.Errorf("no counter name specified")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%s is not implemented for counters", verb)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (counter *Counter) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
|
||||
family, table, _ := getTable(ctx, counter.Family, counter.Table)
|
||||
|
||||
// Special case for delete-by-handle
|
||||
if (verb == deleteVerb || verb == destroyVerb) && counter.Handle != nil {
|
||||
fmt.Fprintf(writer, "%s counter %s %s handle %d", verb, family, table, *counter.Handle)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "%s counter %s %s ", verb, family, table)
|
||||
switch verb {
|
||||
case addVerb, createVerb:
|
||||
fmt.Fprint(writer, counter.Name)
|
||||
if counter.Comment != nil || counter.Packets != nil || counter.Bytes != nil {
|
||||
fmt.Fprintf(writer, " {")
|
||||
if counter.Packets != nil && counter.Bytes != nil {
|
||||
fmt.Fprintf(writer, " packets %d bytes %d ;", *counter.Packets, *counter.Bytes)
|
||||
}
|
||||
if counter.Comment != nil && (verb == addVerb || verb == createVerb) {
|
||||
fmt.Fprintf(writer, " comment %q ;", *counter.Comment)
|
||||
}
|
||||
fmt.Fprintf(writer, " }")
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(writer, counter.Name)
|
||||
}
|
||||
fmt.Fprintf(writer, "\n")
|
||||
}
|
||||
|
|
|
|||
77
vendor/sigs.k8s.io/knftables/transaction.go
generated
vendored
77
vendor/sigs.k8s.io/knftables/transaction.go
generated
vendored
|
|
@ -44,29 +44,23 @@ const (
|
|||
insertVerb verb = "insert"
|
||||
replaceVerb verb = "replace"
|
||||
deleteVerb verb = "delete"
|
||||
destroyVerb verb = "destroy"
|
||||
flushVerb verb = "flush"
|
||||
resetVerb verb = "reset"
|
||||
)
|
||||
|
||||
// populateCommandBuf populates the transaction as series of nft commands to the given bytes.Buffer.
|
||||
func (tx *Transaction) populateCommandBuf(buf *bytes.Buffer) error {
|
||||
if tx.err != nil {
|
||||
return tx.err
|
||||
}
|
||||
|
||||
func (tx *Transaction) populateCommandBuf(buf *bytes.Buffer) {
|
||||
for _, op := range tx.operations {
|
||||
op.obj.writeOperation(op.verb, tx.nftContext, buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the transaction as a string containing the nft commands; if there is
|
||||
// a pending error, it will be output as a comment at the end of the transaction.
|
||||
func (tx *Transaction) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
for _, op := range tx.operations {
|
||||
op.obj.writeOperation(op.verb, tx.nftContext, buf)
|
||||
}
|
||||
|
||||
tx.populateCommandBuf(buf)
|
||||
if tx.err != nil {
|
||||
fmt.Fprintf(buf, "# ERROR: %v", tx.err)
|
||||
}
|
||||
|
|
@ -83,7 +77,7 @@ func (tx *Transaction) operation(verb verb, obj Object) {
|
|||
if tx.err != nil {
|
||||
return
|
||||
}
|
||||
if tx.err = obj.validate(verb); tx.err != nil {
|
||||
if tx.err = obj.validate(verb, tx.nftContext); tx.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -132,10 +126,63 @@ func (tx *Transaction) Flush(obj Object) {
|
|||
tx.operation(flushVerb, obj)
|
||||
}
|
||||
|
||||
// Delete adds an "nft delete" operation to tx, deleting obj. The Delete() call always
|
||||
// succeeds, but if obj does not exist or cannot be deleted based on the information
|
||||
// provided (eg, Handle is required but not set) then an error will be returned when the
|
||||
// transaction is Run.
|
||||
// Delete adds an "nft delete" operation to tx, deleting obj, which must exist. The
|
||||
// Delete() call always succeeds, but if obj does not exist or cannot be deleted based on
|
||||
// the information provided (eg, Handle is required but not set) then an error will be
|
||||
// returned when the transaction is Run.
|
||||
func (tx *Transaction) Delete(obj Object) {
|
||||
tx.operation(deleteVerb, obj)
|
||||
}
|
||||
|
||||
// Reset adds a "nft reset" operation to tx, resetting obj (which must be a Counter).
|
||||
// The Reset() call always succeeds, but if obj does not exist then an error will be
|
||||
// returned when the transaction is Run.
|
||||
func (tx *Transaction) Reset(obj Object) {
|
||||
tx.operation(resetVerb, obj)
|
||||
}
|
||||
|
||||
// Destroy adds an "nft destroy" operation to tx, ensuring that obj does not exist, by
|
||||
// deleting it if it does exist. The Destroy() call always succeeds, but if obj cannot be
|
||||
// deleted based on the information provided (eg, Handle is required but not set) then an
|
||||
// error will be returned when the transaction is Run.
|
||||
//
|
||||
// Support for the actual "nft destroy" command requires kernel 6.3+ and nft 1.0.7+. You
|
||||
// can create the Interface with the `RequireDestroy` option if you want construction to
|
||||
// fail on older hosts. Alternatively, you can create the interface with the
|
||||
// `EmulateDestroy` option, in which case knftables will emulate Destroy by doing an
|
||||
// Add+Delete. In that case, obj must be valid for both an Add and a Delete. (Even if the
|
||||
// system you are on supports destroy, you may only call Destroy() in a
|
||||
// backward-compatible way if you are using `EmulateDestroy`.) In particular, this means:
|
||||
//
|
||||
// - You can only Destroy() objects by Name or Key, not by Handle.
|
||||
// - You can't Destroy() a Rule (since they can only be deleted by Handle).
|
||||
// - You do not need to include optional values in obj (e.g. base chain properties) but
|
||||
// if you do include them, they need to be correct.
|
||||
// - When Destroy()ing a Set or Map you must include the correct Type.
|
||||
// - When Destroy()ing a Map Element you must include the correct Value.
|
||||
func (tx *Transaction) Destroy(obj Object) {
|
||||
if tx.err != nil {
|
||||
return
|
||||
}
|
||||
if tx.err = obj.validate(destroyVerb, tx.nftContext); tx.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tx.emulateDestroy {
|
||||
err := obj.validate(addVerb, tx.nftContext)
|
||||
if err == nil {
|
||||
err = obj.validate(deleteVerb, tx.nftContext)
|
||||
}
|
||||
if err != nil {
|
||||
tx.err = fmt.Errorf("object is not compatible with EmulateDestroy: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tx.emulateDestroy && !tx.nftContext.hasDestroy {
|
||||
tx.operations = append(tx.operations, operation{verb: addVerb, obj: obj})
|
||||
tx.operations = append(tx.operations, operation{verb: deleteVerb, obj: obj})
|
||||
} else {
|
||||
tx.operations = append(tx.operations, operation{verb: destroyVerb, obj: obj})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
152
vendor/sigs.k8s.io/knftables/types.go
generated
vendored
152
vendor/sigs.k8s.io/knftables/types.go
generated
vendored
|
|
@ -33,7 +33,7 @@ const (
|
|||
// implement this interface.
|
||||
type Object interface {
|
||||
// validate validates an object for an operation
|
||||
validate(verb verb) error
|
||||
validate(verb verb, ctx *nftContext) error
|
||||
|
||||
// writeOperation writes out an "nft" operation involving the object. It assumes
|
||||
// that the object has been validated.
|
||||
|
|
@ -43,7 +43,7 @@ type Object interface {
|
|||
// command. line is the part of the line after "nft add <type> <family> <tablename>"
|
||||
// (so for most types it starts with the object name).
|
||||
// If error is returned, Object's fields may be partially filled, therefore Object should not be used.
|
||||
parse(line string) error
|
||||
parse(family Family, table, line string) error
|
||||
}
|
||||
|
||||
// Family is an nftables family
|
||||
|
|
@ -71,13 +71,33 @@ const (
|
|||
NetDevFamily Family = "netdev"
|
||||
)
|
||||
|
||||
// TableFlag represents a table flag
|
||||
type TableFlag string
|
||||
|
||||
const (
|
||||
// DormantFlag indicates that a table is not currently evaluated. (Its base chains
|
||||
// are unregistered.)
|
||||
DormantFlag TableFlag = "dormant"
|
||||
)
|
||||
|
||||
// Table represents an nftables table.
|
||||
type Table struct {
|
||||
// Family is the nftables family of the table. You do not normally need to fill
|
||||
// this in because it will be filled in for you automatically from the Interface.
|
||||
Family Family
|
||||
|
||||
// Name is the name of the table. You do not normally need to fill this in
|
||||
// because it will be filled in for you automatically from the Interface.
|
||||
Name string
|
||||
|
||||
// Comment is an optional comment for the table. (Requires kernel >= 5.10 and
|
||||
// nft >= 0.9.7; otherwise this field will be silently ignored. Requires
|
||||
// nft >= 1.0.8 to include comments in List() results.)
|
||||
Comment *string
|
||||
|
||||
// Flags are the table flags
|
||||
Flags []TableFlag
|
||||
|
||||
// Handle is an identifier that can be used to uniquely identify an object when
|
||||
// deleting it. When adding a new object, this must be nil.
|
||||
Handle *int
|
||||
|
|
@ -185,9 +205,31 @@ const (
|
|||
SNATPriority BaseChainPriority = "srcnat"
|
||||
)
|
||||
|
||||
// BaseChainPolicy sets what happens to packets not explicitly accepted or refused by a
|
||||
// base chain.
|
||||
type BaseChainPolicy string
|
||||
|
||||
const (
|
||||
// AcceptPolicy, which is the default, accepts any unmatched packets (though,
|
||||
// as with any other nftables chain, a later chain can drop or reject it).
|
||||
AcceptPolicy BaseChainPolicy = "accept"
|
||||
|
||||
// DropPolicy drops any unmatched packets.
|
||||
DropPolicy BaseChainPolicy = "drop"
|
||||
)
|
||||
|
||||
// Chain represents an nftables chain; either a "base chain" (if Type, Hook, and Priority
|
||||
// are specified), or a "regular chain" (if they are not).
|
||||
type Chain struct {
|
||||
// Family is the nftables family of the chain's table. You do not normally need to
|
||||
// fill this in because it will be filled in for you automatically from the
|
||||
// Interface.
|
||||
Family Family
|
||||
|
||||
// Table is the name of the chain's table. You do not normally need to fill this
|
||||
// in because it will be filled in for you automatically from the Interface.
|
||||
Table string
|
||||
|
||||
// Name is the name of the chain.
|
||||
Name string
|
||||
|
||||
|
|
@ -201,6 +243,10 @@ type Chain struct {
|
|||
// a regular chain. You can call ParsePriority() to convert this to a number.
|
||||
Priority *BaseChainPriority
|
||||
|
||||
// Policy is the policy for packets not explicitly accepted or refused by a base
|
||||
// chain.
|
||||
Policy *BaseChainPolicy
|
||||
|
||||
// Device is the network interface that the chain is attached to; this must be set
|
||||
// for a base chain connected to the "ingress" or "egress" hooks, and unset for
|
||||
// all other chains.
|
||||
|
|
@ -218,6 +264,15 @@ type Chain struct {
|
|||
|
||||
// Rule represents a rule in a chain
|
||||
type Rule struct {
|
||||
// Family is the nftables family of the rule's table. You do not normally need to
|
||||
// fill this in because it will be filled in for you automatically from the
|
||||
// Interface.
|
||||
Family Family
|
||||
|
||||
// Table is the name of the rule's table. You do not normally need to fill this
|
||||
// in because it will be filled in for you automatically from the Interface.
|
||||
Table string
|
||||
|
||||
// Chain is the name of the chain that contains this rule
|
||||
Chain string
|
||||
|
||||
|
|
@ -277,6 +332,15 @@ const (
|
|||
|
||||
// Set represents the definition of an nftables set (but not its elements)
|
||||
type Set struct {
|
||||
// Family is the nftables family of the set's table. You do not normally need to
|
||||
// fill this in because it will be filled in for you automatically from the
|
||||
// Interface.
|
||||
Family Family
|
||||
|
||||
// Table is the name of the set's table. You do not normally need to fill this
|
||||
// in because it will be filled in for you automatically from the Interface.
|
||||
Table string
|
||||
|
||||
// Name is the name of the set.
|
||||
Name string
|
||||
|
||||
|
|
@ -322,6 +386,15 @@ type Set struct {
|
|||
|
||||
// Map represents the definition of an nftables map (but not its elements)
|
||||
type Map struct {
|
||||
// Family is the nftables family of the map's table. You do not normally need to
|
||||
// fill this in because it will be filled in for you automatically from the
|
||||
// Interface.
|
||||
Family Family
|
||||
|
||||
// Table is the name of the map's table. You do not normally need to fill this
|
||||
// in because it will be filled in for you automatically from the Interface.
|
||||
Table string
|
||||
|
||||
// Name is the name of the map.
|
||||
Name string
|
||||
|
||||
|
|
@ -363,6 +436,15 @@ type Map struct {
|
|||
|
||||
// Element represents a set or map element
|
||||
type Element struct {
|
||||
// Family is the nftables family of the element's table. You do not normally need
|
||||
// to fill this in because it will be filled in for you automatically from the
|
||||
// Interface.
|
||||
Family Family
|
||||
|
||||
// Table is the name of the element's table. You do not normally need to fill this
|
||||
// in because it will be filled in for you automatically from the Interface.
|
||||
Table string
|
||||
|
||||
// Set is the name of the set that contains this element (or the empty string if
|
||||
// this is a map element.)
|
||||
Set string
|
||||
|
|
@ -382,3 +464,69 @@ type Element struct {
|
|||
// Comment is an optional comment for the element
|
||||
Comment *string
|
||||
}
|
||||
|
||||
type FlowtableIngressPriority string
|
||||
|
||||
const (
|
||||
// FilterIngressPriority is the priority for the filter value in the Ingress hook
|
||||
// that stands for 0.
|
||||
FilterIngressPriority FlowtableIngressPriority = "filter"
|
||||
)
|
||||
|
||||
// Flowtable represents an nftables flowtable.
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Flowtables
|
||||
type Flowtable struct {
|
||||
// Family is the nftables family of the flowtable's table. You do not normally
|
||||
// need to fill this in because it will be filled in for you automatically from
|
||||
// the Interface.
|
||||
Family Family
|
||||
|
||||
// Table is the name of the flowtable's table. You do not normally need to fill
|
||||
// this in because it will be filled in for you automatically from the Interface.
|
||||
Table string
|
||||
|
||||
// Name is the name of the flowtable.
|
||||
Name string
|
||||
|
||||
// The Priority can be a signed integer or FlowtableIngressPriority which stands for 0.
|
||||
// Addition and subtraction can be used to set relative priority, e.g. filter + 5 equals to 5.
|
||||
Priority *FlowtableIngressPriority
|
||||
|
||||
// The Devices are specified as iifname(s) of the input interface(s) of the traffic
|
||||
// that should be offloaded.
|
||||
Devices []string
|
||||
|
||||
// Handle is an identifier that can be used to uniquely identify an object when
|
||||
// deleting it. When adding a new object, this must be nil
|
||||
Handle *int
|
||||
}
|
||||
|
||||
// Counter represents named counter
|
||||
type Counter struct {
|
||||
// Family is the nftables family of the counter's table. You do not normally
|
||||
// need to fill this in because it will be filled in for you automatically from
|
||||
// the Interface.
|
||||
Family Family
|
||||
|
||||
// Table is the name of the counter's table. You do not normally need to fill
|
||||
// this in because it will be filled in for you automatically from the Interface.
|
||||
Table string
|
||||
|
||||
// Name is the name of the named counter
|
||||
Name string
|
||||
|
||||
// Comment is an optional comment for the counter
|
||||
Comment *string
|
||||
|
||||
// Packets represents numbers of packets tracked by the counter.
|
||||
// This will be filled in by ListCounters() but can be nil when creating new counter.
|
||||
Packets *uint64
|
||||
|
||||
// Bytes represents numbers of bytes tracked by the counter.
|
||||
// This will be filled in by ListCounters() but can be nil when creating new counter.
|
||||
Bytes *uint64
|
||||
|
||||
// Handle is an identifier that can be used to uniquely identify an object when
|
||||
// deleting it. When adding a new object, this must be nil
|
||||
Handle *int
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue