Merge pull request #122920 from danwinship/knftables-migration

Update knftables, with new sigs.k8s.io module name
This commit is contained in:
Kubernetes Prow Robot 2024-01-26 07:14:16 +01:00 committed by GitHub
commit e023511deb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 577 additions and 130 deletions

View File

@ -1,4 +1,4 @@
= vendor/github.com/danwinship/knftables licensed under: =
= vendor/sigs.k8s.io/knftables licensed under: =
Apache License
Version 2.0, January 2004
@ -202,4 +202,4 @@
See the License for the specific language governing permissions and
limitations under the License.
= vendor/github.com/danwinship/knftables/LICENSE 86d3f3a95c324c9479bd8986968f4327
= vendor/sigs.k8s.io/knftables/LICENSE 86d3f3a95c324c9479bd8986968f4327

2
go.mod
View File

@ -24,7 +24,6 @@ require (
github.com/coreos/go-systemd/v22 v22.5.0
github.com/cpuguy83/go-md2man/v2 v2.0.2
github.com/cyphar/filepath-securejoin v0.2.4
github.com/danwinship/knftables v0.0.13
github.com/distribution/reference v0.5.0
github.com/docker/go-units v0.5.0
github.com/emicklei/go-restful/v3 v3.11.0
@ -122,6 +121,7 @@ require (
k8s.io/sample-apiserver v0.0.0
k8s.io/system-validators v1.8.0
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
sigs.k8s.io/knftables v0.0.14
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
sigs.k8s.io/yaml v1.3.0
)

4
go.sum
View File

@ -283,8 +283,6 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/danwinship/knftables v0.0.13 h1:89Ieiia6MMfXWQF9dyaou1CwBU8h8sHa2Zo3OlY2o04=
github.com/danwinship/knftables v0.0.13/go.mod h1:OzipaBQqkQAIbVnafTGyHgfFbjWTJecrA7/XNLNMO5E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -1294,6 +1292,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 h1:TgtAeesdhpm2S
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0/go.mod h1:VHVDI/KrK4fjnV61bE2g3sA7tiETLn8sooImelsCx3Y=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/knftables v0.0.14 h1:VzKQoDMCGBOH8c85sGrWSXSPCS0XrIpEfOlcCLBXiC0=
sigs.k8s.io/knftables v0.0.14/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk=
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0=
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=
sigs.k8s.io/kustomize/cmd/config v0.11.2/go.mod h1:PCpHxyu10daTnbMfn3xhH1vppn7L8jsS3qpRKXb7Lkc=

View File

@ -29,13 +29,13 @@ import (
"strings"
"testing"
"github.com/danwinship/knftables"
"github.com/google/go-cmp/cmp"
"github.com/lithammer/dedent"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
netutils "k8s.io/utils/net"
"sigs.k8s.io/knftables"
)
// getLine returns a string containing the file and line number of the caller, if

View File

@ -36,8 +36,6 @@ import (
"sync/atomic"
"time"
"github.com/danwinship/knftables"
v1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/types"
@ -57,6 +55,7 @@ import (
utilexec "k8s.io/utils/exec"
netutils "k8s.io/utils/net"
"k8s.io/utils/ptr"
"sigs.k8s.io/knftables"
)
const (

View File

@ -26,7 +26,6 @@ import (
"testing"
"time"
"github.com/danwinship/knftables"
"github.com/lithammer/dedent"
"github.com/stretchr/testify/assert"
@ -50,6 +49,7 @@ import (
"k8s.io/kubernetes/pkg/util/async"
netutils "k8s.io/utils/net"
"k8s.io/utils/ptr"
"sigs.k8s.io/knftables"
)
// Conventions for tests using NewFakeProxier:

6
vendor/modules.txt vendored
View File

@ -141,9 +141,6 @@ github.com/cpuguy83/go-md2man/v2/md2man
# github.com/cyphar/filepath-securejoin v0.2.4
## explicit; go 1.13
github.com/cyphar/filepath-securejoin
# github.com/danwinship/knftables v0.0.13
## explicit; go 1.20
github.com/danwinship/knftables
# github.com/davecgh/go-spew v1.1.1
## explicit
github.com/davecgh/go-spew/spew
@ -2331,6 +2328,9 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client
## explicit; go 1.18
sigs.k8s.io/json
sigs.k8s.io/json/internal/golang/encoding/json
# sigs.k8s.io/knftables v0.0.14
## explicit; go 1.20
sigs.k8s.io/knftables
# sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3
## explicit; go 1.19
sigs.k8s.io/kustomize/api/filters/annotations

126
vendor/sigs.k8s.io/knftables/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,126 @@
# ChangeLog
## v0.0.14
- Renamed the package `"sigs.k8s.io/knftables"`, reflecting its new
home at https://github.com/kubernetes-sigs/knftables/
- Improvements to `Fake`:
- `Fake.Run()` is now properly transactional, and will have no
side effects if an error occurs.
- `Fake.Dump()` now outputs all `add chain`, `add set`, and `add
table` commands before any `add rule` and `add element`
commands, to ensure that the dumped ruleset can be passed to
`nft -f` without errors.
- Conversely, `Fake.Run()` now does enough parsing of rules and
elements that it will notice rules that do lookups in
non-existent sets/maps, and rules/verdicts that jump to
non-existent chains, so it can error out in those cases.
- Added `nft.Check()`, which is like `nft.Run()`, but using
`nft --check`.
- Fixed support for ingress and egress hooks (by adding
`Chain.Device`).
## v0.0.13
- Fixed a bug in `Fake.Run` where it was not properly returning "not
found" / "already exists" errors.
## v0.0.12
- Renamed the package from `"github.com/danwinship/nftables"` to
`"github.com/danwinship/knftables"`, for less ambiguity.
- Added `NameLengthMax` and `CommentLengthMax` constants.
- Changed serialization of `Chain` to convert string-valued `Priority`
to numeric form, if possible.
- (The `v0.0.11` tag exists but is not usable due to a bad `go.mod`)
## v0.0.10
- Dropped `Define`, because nft defines turned out to not work the way
I thought (in particular, you can't do "$IP daddr"), so they end up
not really being useful for our purposes.
- Made `NewTransaction` a method on `Interface` rather than a
top-level function.
- Added `Transaction.String()`, for debugging
- Fixed serialization of set/map elements with timeouts
- Added special treament for `"@"` to `Concat`
- Changed `nftables.New()` to return an `error` (doing the work that
used to be done by `nft.Present()`.)
- Add autodetection for "object comment" support, and have
serialization just ignore comments on `Table`/`Chain`/`Set`/`Map` if
nft or the kernel does not support them.
- Renamed `Optional()` to `PtrTo()`
## v0.0.9
- Various tweaks to `Element`:
- Changed `Key` and `Value` from `string` to `[]string` to better
support concatenated types (and dropped the `Join()` and
`Split()` helper functions that were previously used to join and
split concatenated values).
- Split `Name` into separate `Set` and `Map` fields, which make it
clearer what is being named, and are more consistent with
`Rule.Chain`, and provide more redundancy for distinguishing set
elements from map elements.
- Fixed serialization of map elements with a comments.
- Rewrote `ListElements` and `ListRules` to use `nft -j`, for easier /
more reliable parsing. But this meant that `ListRules` no longer
returns the actual text of the rule.
## v0.0.8
- Fixed `Fake.List` / `Fake.ListRules` / `Fake.ListElements` to return
errors that would be properly recognized by
`IsNotFound`/`IsAlreadyExists`.
## v0.0.7
- Implemented `tx.Create`, `tx.Insert`, `tx.Replace`
- Replaced `tx.AddRule` with the `Concat` function
## v0.0.6
- Added `IsNotFound` and `IsAlreadyExists` error-checking functions
## v0.0.5
- Moved `Define` from `Transaction` to `Interface`
## v0.0.3, v0.0.4
- Improvements to `Fake` to handle `Rule` and `Element`
deletion/overwrite.
- Added `ListRules` and `ListElements`
- (The `v0.0.3` and `v0.0.4` tags are identical.)
## v0.0.2
- Made `Interface` be specific to a single family and table. (Before,
that was specified at the `Transaction` level.)
## v0.0.1
- Initial "release"

28
vendor/sigs.k8s.io/knftables/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,28 @@
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
## Getting Started
We have full documentation on how to get started contributing here:
<!---
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
-->
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) - Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](https://k8s.dev/guide) - Main contributor documentation, or you can just jump directly to the [contributing page](https://k8s.dev/docs/guide/contributing/)
- [Contributor Cheat Sheet](https://k8s.dev/cheatsheet) - Common resources for existing developers
## Mentorship
- [Mentoring Initiatives](https://k8s.dev/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
## Contact Information
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)

7
vendor/sigs.k8s.io/knftables/OWNERS generated vendored Normal file
View File

@ -0,0 +1,7 @@
# See the OWNERS docs at https://go.k8s.io/owners
reviewers:
- aojea
- danwinship
approvers:
- danwinship

View File

@ -3,13 +3,30 @@
This is a library for using nftables from Go.
It is not intended to support arbitrary use cases, but instead
specifically focuses on supporing Kubernetes components which are
specifically focuses on supporting Kubernetes components which are
using nftables in the way that nftables is supposed to be used (as
opposed to using nftables in a naively-translated-from-iptables way,
or using nftables to do totally valid things that aren't the sorts of
things Kubernetes components are likely to need to do).
It is still under development and is not API stable.
It is still under development and is not yet API stable. (See the
section on "Possible future changes" below.)
The library is implemented as a wrapper around the `nft` CLI, because
the CLI API is the only well-documented interface to nftables.
Although it would be possible to use netlink directly (and some other
golang-based nftables libraries do this), that would result in an API
that is quite different from all documented examples of nftables usage
(e.g. the man pages and the [nftables wiki](http://wiki.nftables.org/))
because there is no easy way to convert the "standard" representation
of nftables rules into the netlink form.
(Actually, that's not quite true: the `nft` CLI is just a thin wrapper
around `libnftables`, and it would be possible for knftables to use
cgo to invoke that library instead of using an external binary.
However, this would be harder to build and ship, so I'm not bothering
with that for now. But this could be done in the future without
needing to change knftables's API.)
## Usage
@ -23,6 +40,12 @@ 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.)
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`
@ -123,10 +146,6 @@ for use in unit tests. Use `knftables.NewFake()` instead of
same. See `fake.go` for more details of the public APIs for examining
the current state of the fake nftables database.
Note that at the present time, `fake.Run()` is not actually
transactional, so unit tests that rely on things not being changed if
a transaction fails partway through will not work as expected.
## Missing APIs
Various top-level object types are not yet supported (notably the
@ -140,17 +159,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.
I've considered changing the semantics of `tx.Add(obj)` so that
`obj.Handle` is filled in with the new object's handle on return from
`Run()`, for ease of deleting later. (This would be implemented by
using the `--handle` (`-a`) and `--echo` (`-e`) flags to `nft add`.)
However, this would require potentially difficult parsing of the `nft`
output. `ListRules` fills in the handles of the rules it returns, so
it's possible to find out a rule's handle after the fact that way. For
other supported object types, either handles don't exist (`Element`)
or you don't really need to know their handles because it's possible
to delete by name instead (`Table`, `Chain`, `Set`, `Map`).
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
@ -164,14 +172,96 @@ of the rules in the chain, but want to know their handles, or (b) you
can recognize the rules you are looking for by their comments, rather
than the rule bodies.
# Design Notes
## Possible future changes
The library works by invoking the `nft` binary. "Write" operations are
implemented with the ordinary plain-text API, while "read" operations
are implemented with the JSON API, for parseability.
### `nft` output parsing
The fact that the API uses functions and objects (e.g.
`tx.Add(&knftables.Chain{...})`) rather than just specifying everything
as textual input to `nft` (e.g. `tx.Exec("add chain ...")`) is mostly
just because it's _much_ easier to have a fake implementation for unit
tests this way.
`nft`'s output is documented and standardized, so it ought to be
possible for us to extract better error messages in the event of a
transaction failure.
Additionally, if we used the `--echo` (`-e`) and `--handle` (`-a`)
flags, we could learn the handles associated with newly-created
objects in a transaction, and return these to the caller somehow.
(E.g., by setting the `Handle` field in the object that had been
passed to `tx.Add` when the transaction is run.)
(For now, `ListRules` fills in the handles of the rules it returns, so
it's possible to find out a rule's handle after the fact that way. For
other supported object types, either handles don't exist (`Element`)
or you don't really need to know their handles because it's possible
to delete by name instead (`Table`, `Chain`, `Set`, `Map`).)
### List APIs
The fact that `List` works completely differently from `ListRules` and
`ListElements` is a historical artifact.
I would like to have a single function
```golang
List[T Object](ctx context.Context, template T) ([]T, error)
```
So you could say
```golang
elements, err := nft.List(ctx, &knftables.Element{Set: "myset"})
```
to list the elements of "myset". But this doesn't actually compile
("`syntax error: method must have no type parameters`") because
allowing that would apparently introduce extremely complicated edge
cases in Go generics.
### Set/map type representation
There is currently an annoying asymmetry in the representation of
concatenated types between `Set`/`Map` and `Element`, where the former
uses a string containing `nft` syntax, and the latter uses an array:
```golang
tx.Add(&knftables.Set{
Name: "firewall",
Type: "ipv4_addr . inet_proto . inet_service",
})
tx.Add(&knftables.Element{
Set: "firewall",
Key: []string{"10.1.2.3", "tcp", "80"},
})
```
This will probably be fixed at some point, which may result in a
change to how the `type` vs `typeof` distinction is handled as well.
### Optimization and rule representation
We will need to optimize the performance of large transactions. One
change that is likely is to avoid pre-concatenating rule elements in
cases like:
```golang
tx.Add(&knftables.Rule{
Chain: "mychain",
Rule: knftables.Concat(
"ip daddr", destIP,
"ip protocol", "tcp",
"th port", destPort,
"jump", destChain,
)
})
```
This will presumably require a change to `knftables.Rule` and/or
`knftables.Concat()` but I'm not sure exactly what it will be.
## Community, discussion, contribution, and support
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)
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for more information about
contributing. Participation in the Kubernetes community is governed by
the [Kubernetes Code of Conduct](code-of-conduct.md).

13
vendor/sigs.k8s.io/knftables/SECURITY_CONTACTS generated vendored Normal file
View File

@ -0,0 +1,13 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Security Response Committee to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
danwinship

3
vendor/sigs.k8s.io/knftables/code-of-conduct.md generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -154,17 +154,30 @@ func (fake *Fake) NewTransaction() *Transaction {
// Run is part of Interface
func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
updatedTable, err := fake.run(tx)
if err == nil {
fake.Table = updatedTable
}
return err
}
// Check is part of Interface
func (fake *Fake) Check(ctx context.Context, tx *Transaction) error {
_, err := fake.run(tx)
return err
}
func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
if tx.err != nil {
return tx.err
return nil, tx.err
}
// FIXME: not actually transactional!
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 fake.Table == nil {
if updatedTable == nil {
if _, ok := op.obj.(*Table); !ok {
return notFoundError("no such table \"%s %s\"", fake.family, fake.table)
return nil, notFoundError("no such table \"%s %s\"", fake.family, fake.table)
}
}
@ -174,37 +187,37 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
switch obj := op.obj.(type) {
case *Table:
err := checkExists(op.verb, "table", fake.table, fake.Table != nil)
err := checkExists(op.verb, "table", fake.table, updatedTable != nil)
if err != nil {
return err
return nil, err
}
switch op.verb {
case flushVerb:
fake.Table = nil
updatedTable = nil
fallthrough
case addVerb, createVerb:
if fake.Table != nil {
if updatedTable != nil {
continue
}
table := *obj
table.Handle = PtrTo(fake.nextHandle)
fake.Table = &FakeTable{
updatedTable = &FakeTable{
Table: table,
Chains: make(map[string]*FakeChain),
Sets: make(map[string]*FakeSet),
Maps: make(map[string]*FakeMap),
}
case deleteVerb:
fake.Table = nil
updatedTable = nil
default:
return fmt.Errorf("unhandled operation %q", op.verb)
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}
case *Chain:
existingChain := fake.Table.Chains[obj.Name]
existingChain := updatedTable.Chains[obj.Name]
err := checkExists(op.verb, "chain", obj.Name, existingChain != nil)
if err != nil {
return err
return nil, err
}
switch op.verb {
case addVerb, createVerb:
@ -213,27 +226,27 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
}
chain := *obj
chain.Handle = PtrTo(fake.nextHandle)
fake.Table.Chains[obj.Name] = &FakeChain{
updatedTable.Chains[obj.Name] = &FakeChain{
Chain: chain,
}
case flushVerb:
existingChain.Rules = nil
case deleteVerb:
// FIXME delete-by-handle
delete(fake.Table.Chains, obj.Name)
delete(updatedTable.Chains, obj.Name)
default:
return fmt.Errorf("unhandled operation %q", op.verb)
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}
case *Rule:
existingChain := fake.Table.Chains[obj.Chain]
existingChain := updatedTable.Chains[obj.Chain]
if existingChain == nil {
return notFoundError("no such chain %q", obj.Chain)
return nil, notFoundError("no such chain %q", obj.Chain)
}
if op.verb == deleteVerb {
i := findRule(existingChain.Rules, *obj.Handle)
if i == -1 {
return notFoundError("no rule with handle %d", *obj.Handle)
return nil, notFoundError("no rule with handle %d", *obj.Handle)
}
existingChain.Rules = append(existingChain.Rules[:i], existingChain.Rules[i+1:]...)
continue
@ -244,15 +257,19 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
if rule.Handle != nil {
refRule = findRule(existingChain.Rules, *obj.Handle)
if refRule == -1 {
return notFoundError("no rule with handle %d", *obj.Handle)
return nil, notFoundError("no rule with handle %d", *obj.Handle)
}
} else if obj.Index != nil {
if *obj.Index >= len(existingChain.Rules) {
return notFoundError("no rule with index %d", *obj.Index)
return nil, notFoundError("no rule with index %d", *obj.Index)
}
refRule = *obj.Index
}
if err := checkRuleRefs(obj, updatedTable); err != nil {
return nil, err
}
switch op.verb {
case addVerb:
if refRule == -1 {
@ -271,14 +288,14 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
case replaceVerb:
existingChain.Rules[refRule] = &rule
default:
return fmt.Errorf("unhandled operation %q", op.verb)
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}
case *Set:
existingSet := fake.Table.Sets[obj.Name]
existingSet := updatedTable.Sets[obj.Name]
err := checkExists(op.verb, "set", obj.Name, existingSet != nil)
if err != nil {
return err
return nil, err
}
switch op.verb {
case addVerb, createVerb:
@ -287,22 +304,22 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
}
set := *obj
set.Handle = PtrTo(fake.nextHandle)
fake.Table.Sets[obj.Name] = &FakeSet{
updatedTable.Sets[obj.Name] = &FakeSet{
Set: set,
}
case flushVerb:
existingSet.Elements = nil
case deleteVerb:
// FIXME delete-by-handle
delete(fake.Table.Sets, obj.Name)
delete(updatedTable.Sets, obj.Name)
default:
return fmt.Errorf("unhandled operation %q", op.verb)
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}
case *Map:
existingMap := fake.Table.Maps[obj.Name]
existingMap := updatedTable.Maps[obj.Name]
err := checkExists(op.verb, "map", obj.Name, existingMap != nil)
if err != nil {
return err
return nil, err
}
switch op.verb {
case addVerb:
@ -311,29 +328,29 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
}
mapObj := *obj
mapObj.Handle = PtrTo(fake.nextHandle)
fake.Table.Maps[obj.Name] = &FakeMap{
updatedTable.Maps[obj.Name] = &FakeMap{
Map: mapObj,
}
case flushVerb:
existingMap.Elements = nil
case deleteVerb:
// FIXME delete-by-handle
delete(fake.Table.Maps, obj.Name)
delete(updatedTable.Maps, obj.Name)
default:
return fmt.Errorf("unhandled operation %q", op.verb)
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}
case *Element:
if len(obj.Value) == 0 {
existingSet := fake.Table.Sets[obj.Set]
existingSet := updatedTable.Sets[obj.Set]
if existingSet == nil {
return notFoundError("no such set %q", obj.Set)
return nil, notFoundError("no such set %q", obj.Set)
}
switch op.verb {
case addVerb, createVerb:
element := *obj
if i := findElement(existingSet.Elements, element.Key); i != -1 {
if op.verb == createVerb {
return existsError("element %q already exists", strings.Join(element.Key, " . "))
return nil, existsError("element %q already exists", strings.Join(element.Key, " . "))
}
existingSet.Elements[i] = &element
} else {
@ -344,22 +361,25 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
if i := findElement(existingSet.Elements, element.Key); i != -1 {
existingSet.Elements = append(existingSet.Elements[:i], existingSet.Elements[i+1:]...)
} else {
return notFoundError("no such element %q", strings.Join(element.Key, " . "))
return nil, notFoundError("no such element %q", strings.Join(element.Key, " . "))
}
default:
return fmt.Errorf("unhandled operation %q", op.verb)
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}
} else {
existingMap := fake.Table.Maps[obj.Map]
existingMap := updatedTable.Maps[obj.Map]
if existingMap == nil {
return notFoundError("no such map %q", obj.Map)
return nil, notFoundError("no such map %q", obj.Map)
}
if err := checkElementRefs(obj, updatedTable); err != nil {
return nil, err
}
switch op.verb {
case addVerb, createVerb:
element := *obj
if i := findElement(existingMap.Elements, element.Key); i != -1 {
if op.verb == createVerb {
return existsError("element %q already exists", strings.Join(element.Key, ". "))
return nil, existsError("element %q already exists", strings.Join(element.Key, ". "))
}
existingMap.Elements[i] = &element
} else {
@ -370,18 +390,18 @@ func (fake *Fake) Run(ctx context.Context, tx *Transaction) error {
if i := findElement(existingMap.Elements, element.Key); i != -1 {
existingMap.Elements = append(existingMap.Elements[:i], existingMap.Elements[i+1:]...)
} else {
return notFoundError("no such element %q", strings.Join(element.Key, " . "))
return nil, notFoundError("no such element %q", strings.Join(element.Key, " . "))
}
default:
return fmt.Errorf("unhandled operation %q", op.verb)
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}
}
default:
return fmt.Errorf("unhandled object type %T", op.obj)
return nil, fmt.Errorf("unhandled object type %T", op.obj)
}
}
return nil
return updatedTable, nil
}
func checkExists(verb verb, objectType, name string, exists bool) error {
@ -401,9 +421,48 @@ func checkExists(verb verb, objectType, name string, exists bool) error {
return nil
}
// Dump dumps the current contents of fake, in a way that looks like an nft transaction,
// but not actually guaranteed to be usable as such. (e.g., chains may be referenced
// before they are created, etc)
// checkRuleRefs checks for chains, sets, and maps referenced by rule in table
func checkRuleRefs(rule *Rule, table *FakeTable) error {
words := strings.Split(rule.Rule, " ")
for i, word := range words {
if strings.HasPrefix(word, "@") {
name := word[1:]
if i > 0 && (words[i] == "map" || words[i] == "vmap") {
if table.Maps[name] == nil {
return notFoundError("no such map %q", name)
}
} else {
// recent nft lets you use a map in a set lookup
if table.Sets[name] == nil && table.Maps[name] == nil {
return notFoundError("no such set %q", name)
}
}
} else if (word == "goto" || word == "jump") && i < len(words)-1 {
name := words[i+1]
if table.Chains[name] == nil {
return notFoundError("no such chain %q", name)
}
}
}
return nil
}
// checkElementRefs checks for chains referenced by an element
func checkElementRefs(element *Element, table *FakeTable) error {
if len(element.Value) != 1 {
return nil
}
words := strings.Split(element.Value[0], " ")
if len(words) == 2 && (words[0] == "goto" || words[0] == "jump") {
name := words[1]
if table.Chains[name] == nil {
return notFoundError("no such chain %q", name)
}
}
return nil
}
// 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 ""
@ -412,12 +471,30 @@ func (fake *Fake) Dump() string {
buf := &strings.Builder{}
table := fake.Table
table.writeOperation(addVerb, &fake.nftContext, buf)
chains := sortKeys(table.Chains)
sets := sortKeys(table.Sets)
maps := sortKeys(table.Maps)
for _, cname := range sortKeys(table.Chains) {
// Write out all of the object adds first.
table.writeOperation(addVerb, &fake.nftContext, buf)
for _, cname := range chains {
ch := table.Chains[cname]
ch.writeOperation(addVerb, &fake.nftContext, buf)
}
for _, sname := range sets {
s := table.Sets[sname]
s.writeOperation(addVerb, &fake.nftContext, buf)
}
for _, mname := range maps {
m := table.Maps[mname]
m.writeOperation(addVerb, &fake.nftContext, buf)
}
// Now write their contents.
for _, cname := range chains {
ch := table.Chains[cname]
for _, rule := range ch.Rules {
// Avoid outputing handles
dumpRule := *rule
@ -426,19 +503,14 @@ func (fake *Fake) Dump() string {
dumpRule.writeOperation(addVerb, &fake.nftContext, buf)
}
}
for _, sname := range sortKeys(table.Sets) {
for _, sname := range sets {
s := table.Sets[sname]
s.writeOperation(addVerb, &fake.nftContext, buf)
for _, element := range s.Elements {
element.writeOperation(addVerb, &fake.nftContext, buf)
}
}
for _, mname := range sortKeys(table.Maps) {
for _, mname := range maps {
m := table.Maps[mname]
m.writeOperation(addVerb, &fake.nftContext, buf)
for _, element := range m.Elements {
element.writeOperation(addVerb, &fake.nftContext, buf)
}
@ -474,6 +546,41 @@ func findElement(elements []*Element, key []string) int {
return -1
}
// copy creates a copy of table with new arrays/maps so we can perform a transaction
// on it without changing the original table.
func (table *FakeTable) copy() *FakeTable {
if table == nil {
return nil
}
copy := &FakeTable{
Table: table.Table,
Chains: make(map[string]*FakeChain),
Sets: make(map[string]*FakeSet),
Maps: make(map[string]*FakeMap),
}
for name, chain := range table.Chains {
copy.Chains[name] = &FakeChain{
Chain: chain.Chain,
Rules: append([]*Rule{}, chain.Rules...),
}
}
for name, set := range table.Sets {
copy.Sets[name] = &FakeSet{
Set: set.Set,
Elements: append([]*Element{}, set.Elements...),
}
}
for name, mapObj := range table.Maps {
copy.Maps[name] = &FakeMap{
Map: mapObj.Map,
Elements: append([]*Element{}, mapObj.Elements...),
}
}
return copy
}
// FindElement finds an element of the set with the given key. If there is no matching
// element, it returns nil.
func (s *FakeSet) FindElement(key ...string) *Element {

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -32,6 +32,11 @@ type Interface interface {
// IsAlreadyExists methods can be used to test the result.
Run(ctx context.Context, tx *Transaction) error
// Check does a dry-run of a Transaction (as with `nft --check`) and returns the
// result. The IsNotFound and IsAlreadyExists methods can be used to test the
// result.
Check(ctx context.Context, tx *Transaction) 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.
@ -131,6 +136,23 @@ func (nft *realNFTables) Run(ctx context.Context, tx *Transaction) error {
return err
}
// Check is part of Interface
func (nft *realNFTables) Check(ctx context.Context, tx *Transaction) error {
if tx.err != nil {
return tx.err
}
buf, err := tx.asCommandBuf()
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, nft.path, "--check", "-f", "-")
cmd.Stdin = buf
_, err = nft.exec.Run(cmd)
return err
}
// jsonVal looks up key in json; if it exists and is of type T, it returns (json[key], true).
// Otherwise it returns (_, false).
func jsonVal[T any](json map[string]interface{}, key string) (T, bool) {

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -57,10 +57,17 @@ func (table *Table) writeOperation(verb verb, ctx *nftContext, writer io.Writer)
// Object implementation for Chain
func (chain *Chain) validate(verb verb) error {
if chain.Hook == nil && (chain.Type != nil || chain.Priority != nil) {
return fmt.Errorf("regular chain %q must not specify Type or Priority", chain.Name)
} else if chain.Hook != nil && (chain.Type == nil || chain.Priority == nil) {
return fmt.Errorf("base chain %q must specify Type and Priority", chain.Name)
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.Device != nil {
return fmt.Errorf("regular chain %q must not specify Device", chain.Name)
}
} else {
if chain.Type == nil || chain.Priority == nil {
return fmt.Errorf("base chain %q must specify Type and Priority", chain.Name)
}
}
switch verb {
@ -95,14 +102,19 @@ func (chain *Chain) writeOperation(verb verb, ctx *nftContext, writer io.Writer)
fmt.Fprintf(writer, " {")
if chain.Type != nil {
fmt.Fprintf(writer, " type %s hook %s", *chain.Type, *chain.Hook)
if chain.Device != nil {
fmt.Fprintf(writer, " device %q", *chain.Device)
}
// Parse the priority to a number if we can, because older
// 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 {
fmt.Fprintf(writer, " type %s hook %s priority %d ;", *chain.Type, *chain.Hook, priority)
fmt.Fprintf(writer, " priority %d ;", priority)
} else {
fmt.Fprintf(writer, " type %s hook %s priority %s ;", *chain.Type, *chain.Hook, *chain.Priority)
fmt.Fprintf(writer, " priority %s ;", *chain.Priority)
}
}
if chain.Comment != nil && !ctx.noObjectComments {

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -67,9 +67,9 @@ const (
// Table represents an nftables table.
type Table struct {
// Comment is an optional comment for the table. (Note that this can be specified
// on creation, but depending on the version of /sbin/nft that is available, it
// may not be filled in correctly in the result of a List.)
// 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
// Handle is an identifier that can be used to uniquely identify an object when
@ -77,7 +77,8 @@ type Table struct {
Handle *int
}
// BaseChainType represents the "type" of a "base chain" (ie, a chain that is attached to a hook)
// BaseChainType represents the "type" of a "base chain" (ie, a chain that is attached to a hook).
// See https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_types
type BaseChainType string
const (
@ -93,24 +94,52 @@ const (
RouteType BaseChainType = "route"
)
// BaseChainHook represents the "hook" that a base chain is attached to
// BaseChainHook represents the "hook" that a base chain is attached to.
// See https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_hooks
// and https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks
type BaseChainHook string
// FIXME: document these correctly; virtually all of the existing iptables/nftables
// documentation is slightly wrong, particular wrt locally-generated packets.
const (
PreroutingHook BaseChainHook = "prerouting"
InputHook BaseChainHook = "input"
ForwardHook BaseChainHook = "forward"
OutputHook BaseChainHook = "output"
// PreroutingHook is the "prerouting" stage of packet processing, which is the
// first stage (after "ingress") for inbound ("input path" and "forward path")
// packets.
PreroutingHook BaseChainHook = "prerouting"
// InputHook is the "input" stage of packet processing, which happens after
// "prerouting" for inbound packets being delivered to an interface on this host,
// in this network namespace.
InputHook BaseChainHook = "input"
// ForwardHook is the "forward" stage of packet processing, which happens after
// "prerouting" for inbound packets destined for a non-local IP (i.e. on another
// host or in another network namespace)
ForwardHook BaseChainHook = "forward"
// OutputHook is the "output" stage of packet processing, which is the first stage
// for outbound packets, regardless of their final destination.
OutputHook BaseChainHook = "output"
// PostroutingHook is the "postrouting" stage of packet processing, which is the
// final stage (before "egress") for outbound ("forward path" and "output path")
// packets.
PostroutingHook BaseChainHook = "postrouting"
IngressHook BaseChainHook = "ingress"
EgressHook BaseChainHook = "egress"
// IngressHook is the "ingress" stage of packet processing, in the "netdev" family
// or (with kernel >= 5.10 and nft >= 0.9.7) the "inet" family.
IngressHook BaseChainHook = "ingress"
// EgressHook is the "egress" stage of packet processing, in the "netdev" family
// (with kernel >= 5.16 and nft >= 1.0.1).
EgressHook BaseChainHook = "egress"
)
// BaseChainPriority represents the "priority" of a base chain. In addition to the const
// values, you can also use a signed integer value, or an arithmetic expression consisting
// of a const value followed by "+" or "-" and an integer. Lower values run earlier.
// BaseChainPriority represents the "priority" of a base chain. Lower values run earlier.
// See https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_priority
// and https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
//
// In addition to the const values, you can also use a signed integer value, or an
// arithmetic expression consisting of a const value followed by "+" or "-" and an
// integer.
type BaseChainPriority string
const (
@ -166,7 +195,14 @@ type Chain struct {
// a regular chain. You can call ParsePriority() to convert this to a number.
Priority *BaseChainPriority
// Comment is an optional comment for the object.
// 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.
Device *string
// Comment is an optional comment for the object. (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
// Handle is an identifier that can be used to uniquely identify an object when
@ -243,7 +279,8 @@ type Set struct {
Type string
// TypeOf is the type of the set key as an nftables expression (eg "ip saddr").
// Either Type or TypeOf, but not both, must be non-empty.
// Either Type or TypeOf, but not both, must be non-empty. (Requires at least nft
// 0.9.4, and newer than that for some types.)
TypeOf string
// Flags are the set flags
@ -268,7 +305,8 @@ type Set struct {
// together (only for interval sets)
AutoMerge *bool
// Comment is an optional comment for the object.
// Comment is an optional comment for the object. (Requires kernel >= 5.10 and
// nft >= 0.9.7; otherwise this field will be silently ignored.)
Comment *string
// Handle is an identifier that can be used to uniquely identify an object when
@ -286,7 +324,8 @@ type Map struct {
Type string
// TypeOf is the type of the set key as an nftables expression (eg "ip saddr : verdict").
// Either Type or TypeOf, but not both, must be non-empty.
// Either Type or TypeOf, but not both, must be non-empty. (Requires at least nft 0.9.4,
// and newer than that for some types.)
TypeOf string
// Flags are the map flags
@ -307,7 +346,8 @@ type Map struct {
// Policy is the FIXME
Policy *SetPolicy
// Comment is an optional comment for the object.
// Comment is an optional comment for the object. (Requires kernel >= 5.10 and
// nft >= 0.9.7; otherwise this field will be silently ignored.)
Comment *string
// Handle is an identifier that can be used to uniquely identify an object when

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 Red Hat, Inc.
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.