This is a library for using nftables from Go.
It is not intended to support arbitrary use cases, but instead 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 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)
because there is no easy way to convert the "standard" representation
of nftables rules into the netlink form.
(Actually, it's not quite true that there's no other usable API: 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.)
knftables requires nft version 1.0.1 or later, because earlier versions would download and process the entire ruleset regardless of what you were doing, which, besides being pointlessly inefficient, means that in some cases, other people using new features in their tables could prevent you from modifying your table. (In particular, a change in how some rules are generated starting in nft 1.0.3 triggers a crash in nft 0.9.9 and earlier, even if you aren't looking at the table containing that rule.)
Create an Interface
object to manage operations on a single nftables
table:
nft, err := knftables.New(knftables.IPv4Family, "my-table")
if err != nil {
return fmt.Errorf("no nftables support: %v", err)
}
(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
returns Element
objects and ListRules
returns partial Rule
objects.
chains, err := nft.List(ctx, "chains")
if err != nil {
return fmt.Errorf("could not list chains: %v", err)
}
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
operations to the transaction, and then call nft.Run
on it:
tx := nft.NewTransaction()
tx.Add(&knftables.Chain{
Name: "mychain",
Comment: knftables.PtrTo("this is my chain"),
})
tx.Flush(&knftables.Chain{
Name: "mychain",
})
var destIP net.IP
var destPort uint16
...
tx.Add(&knftables.Rule{
Chain: "mychain",
Rule: knftables.Concat(
"ip daddr", destIP,
"ip protocol", "tcp",
"th port", destPort,
"jump", destChain,
)
})
err := nft.Run(context, tx)
If any operation in the transaction would fail, then Run()
will
return an error and the entire transaction will be ignored. You can
use the knftables.IsNotFound()
and knftables.IsAlreadyExists()
methods to check for those well-known error types. In a large
transaction, there is no supported way to determine exactly which
operation failed.
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 withnft add
tx.Create()
: creates an object, which must not already exist, as withnft create
tx.Flush()
: flushes the contents of a table/chain/set/map, as withnft flush
tx.Delete()
: deletes an object, as withnft delete
tx.Insert()
: inserts a rule before another rule, as withnft insert rule
tx.Replace()
: replaces a rule, as withnft replace rule
The Transaction
methods take arguments of type knftables.Object
.
The currently-supported objects are:
Table
Chain
Rule
Set
Map
Element
Optional fields in objects can be filled in with the help of the
PtrTo()
function, which just returns a pointer to its argument.
Concat()
can be used to concatenate a series of strings, []string
arrays, and other arguments (including numbers, net.IP
s /
net.IPNet
s, and anything else that can be formatted usefully via
fmt.Sprintf("%s")
) together into a single string. This is often
useful when constructing Rule
s.
There is a fake (in-memory) implementation of knftables.Interface
for use in unit tests. Use knftables.NewFake()
instead of
knftables.New()
to create it, and then it should work mostly the
same. See fake.go
for more details of the public APIs for examining
the current state of the fake nftables database.
Various top-level object types are not yet supported (notably the
"stateful objects" like counter
).
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
(or at least "in nftables as used by Kubernetes-ish components that
aren't just blindly copying over old iptables APIs"), because chains
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.
This means that it is only useful when either (a) you know the order
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.
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
).)
The fact that List
works completely differently from ListRules
and
ListElements
is a historical artifact.
I would like to have a single function
List[T Object](ctx context.Context, template T) ([]T, error)
So you could say
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.
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:
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.
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:
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.
knftables is maintained by Kubernetes SIG Network.
See CONTRIBUTING.md
for more information about
contributing. Participation in the Kubernetes community is governed by
the Kubernetes Code of Conduct.