Skip to content

Commit

Permalink
feat: implement nftables backend
Browse files Browse the repository at this point in the history
Implement initial set of backend controllers/resources to handle
nftables chains/rules etc.

Replace the KubeSpan nftables operations with controller-based.

See #4421

Signed-off-by: Andrey Smirnov <[email protected]>
  • Loading branch information
smira committed Nov 27, 2023
1 parent ba827bf commit e46e6a3
Show file tree
Hide file tree
Showing 36 changed files with 9,755 additions and 4,334 deletions.
47 changes: 47 additions & 0 deletions api/resource/definitions/enums/enums.proto
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,46 @@ enum NethelpersLinkType {
LINK_NONE = 65534;
}

// NethelpersMatchOperator is a netfilter match operator.
enum NethelpersMatchOperator {
OPERATOR_EQUAL = 0;
OPERATOR_NOT_EQUAL = 1;
}

// NethelpersNfTablesChainHook wraps nftables.ChainHook for YAML marshaling.
enum NethelpersNfTablesChainHook {
CHAIN_HOOK_PREROUTING = 0;
CHAIN_HOOK_INPUT = 1;
CHAIN_HOOK_FORWARD = 2;
CHAIN_HOOK_OUTPUT = 3;
CHAIN_HOOK_POSTROUTING = 4;
}

// NethelpersNfTablesChainPriority wraps nftables.ChainPriority for YAML marshaling.
enum NethelpersNfTablesChainPriority {
option allow_alias = true;
NETHELPERS_NFTABLESCHAINPRIORITY_UNSPECIFIED = 0;
CHAIN_PRIORITY_FIRST = -2147483648;
CHAIN_PRIORITY_CONNTRACK_DEFRAG = -400;
CHAIN_PRIORITY_RAW = -300;
CHAIN_PRIORITY_SE_LINUX_FIRST = -225;
CHAIN_PRIORITY_CONNTRACK = -200;
CHAIN_PRIORITY_MANGLE = -150;
CHAIN_PRIORITY_NAT_DEST = -100;
CHAIN_PRIORITY_FILTER = 0;
CHAIN_PRIORITY_SECURITY = 50;
CHAIN_PRIORITY_NAT_SOURCE = 100;
CHAIN_PRIORITY_SE_LINUX_LAST = 225;
CHAIN_PRIORITY_CONNTRACK_HELPER = 300;
CHAIN_PRIORITY_LAST = 2147483647;
}

// NethelpersNfTablesVerdict wraps nftables.Verdict for YAML marshaling.
enum NethelpersNfTablesVerdict {
VERDICT_DROP = 0;
VERDICT_ACCEPT = 1;
}

// NethelpersOperationalState wraps rtnetlink.OperationalState for YAML marshaling.
enum NethelpersOperationalState {
OPER_STATE_UNKNOWN = 0;
Expand Down Expand Up @@ -216,6 +256,13 @@ enum NethelpersPrimaryReselect {
PRIMARY_RESELECT_FAILURE = 2;
}

// NethelpersProtocol is a inet protocol.
enum NethelpersProtocol {
NETHELPERS_PROTOCOL_UNSPECIFIED = 0;
PROTOCOL_TCP = 6;
PROTOCOL_UDP = 17;
}

// NethelpersRouteFlag wraps RTM_F_* constants.
enum NethelpersRouteFlag {
NETHELPERS_ROUTEFLAG_UNSPECIFIED = 0;
Expand Down
76 changes: 76 additions & 0 deletions api/resource/definitions/network/network.proto
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,74 @@ message LinkStatusSpec {
bytes permanent_addr = 30;
}

// NfTablesAddressMatch describes the match on the IP address.
message NfTablesAddressMatch {
repeated common.NetIPPrefix include_subnets = 1;
repeated common.NetIPPrefix exclude_subnets = 2;
bool invert = 3;
}

// NfTablesChainSpec describes status of rendered secrets.
message NfTablesChainSpec {
string type = 1;
talos.resource.definitions.enums.NethelpersNfTablesChainHook hook = 2;
talos.resource.definitions.enums.NethelpersNfTablesChainPriority priority = 3;
repeated NfTablesRule rules = 4;
}

// NfTablesClampMSS describes the TCP MSS clamping operation.
//
// MSS is limited by the `MaxMTU` so that:
// - IPv4: MSS = MaxMTU - 40
// - IPv6: MSS = MaxMTU - 60.
message NfTablesClampMSS {
fixed32 mtu = 1;
}

// NfTablesIfNameMatch describes the match on the interface name.
message NfTablesIfNameMatch {
string interface_name = 1;
talos.resource.definitions.enums.NethelpersMatchOperator operator = 2;
}

// NfTablesLayer4Match describes the match on the transport layer protocol.
message NfTablesLayer4Match {
talos.resource.definitions.enums.NethelpersProtocol protocol = 1;
NfTablesPortMatch match_source_port = 2;
NfTablesPortMatch match_destination_port = 3;
}

// NfTablesMark encodes packet mark match/update operation.
//
// When used as a match computes the following condition:
// (mark & mask) ^ xor == value
//
// When used as an update computes the following operation:
// mark = (mark & mask) ^ xor.
message NfTablesMark {
uint32 mask = 1;
uint32 xor = 2;
uint32 value = 3;
}

// NfTablesPortMatch describes the match on the transport layer port.
message NfTablesPortMatch {
repeated PortRange ranges = 1;
}

// NfTablesRule describes a single rule in the nftables chain.
message NfTablesRule {
NfTablesIfNameMatch match_o_if_name = 1;
talos.resource.definitions.enums.NethelpersNfTablesVerdict verdict = 2;
NfTablesMark match_mark = 3;
NfTablesMark set_mark = 4;
NfTablesAddressMatch match_source_address = 5;
NfTablesAddressMatch match_destination_address = 6;
NfTablesLayer4Match match_layer4 = 7;
NfTablesIfNameMatch match_i_if_name = 8;
NfTablesClampMSS clamp_mss = 9;
}

// NodeAddressFilterSpec describes a filter for NodeAddresses.
message NodeAddressFilterSpec {
repeated common.NetIPPrefix include_subnets = 1;
Expand All @@ -188,6 +256,14 @@ message OperatorSpecSpec {
talos.resource.definitions.enums.NetworkConfigLayer config_layer = 7;
}

// PortRange describes a range of ports.
//
// Range is [lo, hi].
message PortRange {
fixed32 lo = 1;
fixed32 hi = 2;
}

// ProbeSpecSpec describes the Probe.
message ProbeSpecSpec {
google.protobuf.Duration interval = 1;
Expand Down
5 changes: 4 additions & 1 deletion hack/structprotogen/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"go/types"
"io"
"regexp"
"slices"
"strconv"
"strings"

Expand Down Expand Up @@ -237,7 +238,9 @@ func (b *ConstBlocks) FormatProtoFile(w io.Writer) error {

fmt.Fprintf(w, "enum %s {\n", block.ProtoMessageName())

if hasDuplicates(block.Consts, func(c Constant) string { return c.Value }) {
hasZeroNotFirstConstValue := slices.IndexFunc(block.Consts, func(c Constant) bool { return c.Value == "0" }) > 0

if hasDuplicates(block.Consts, func(c Constant) string { return c.Value }) || hasZeroNotFirstConstValue {
fmt.Fprintln(w, " option allow_alias = true;")
}

Expand Down
39 changes: 39 additions & 0 deletions internal/app/machined/pkg/adapters/network/ipset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package network

import (
"net/netip"

"go4.org/netipx"
)

// BuildIPSet builds an IPSet from the given include and exclude prefixes.
func BuildIPSet(include, exclude []netip.Prefix) (*netipx.IPSet, error) {
var builder netipx.IPSetBuilder

for _, pfx := range include {
builder.AddPrefix(pfx)
}

for _, pfx := range exclude {
builder.RemovePrefix(pfx)
}

return builder.IPSet()
}

// SplitIPSet splits the given IPSet into IPv4 and IPv6 ranges.
func SplitIPSet(set *netipx.IPSet) (ipv4, ipv6 []netipx.IPRange) {
for _, rng := range set.Ranges() {
if rng.From().Is4() {
ipv4 = append(ipv4, rng)
} else {
ipv6 = append(ipv6, rng)
}
}

return
}
58 changes: 58 additions & 0 deletions internal/app/machined/pkg/adapters/network/ipset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package network_test

import (
"net/netip"
"testing"

"github.com/siderolabs/gen/xslices"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go4.org/netipx"

"github.com/siderolabs/talos/internal/app/machined/pkg/adapters/network"
)

func TestBuildIPSet(t *testing.T) {
ipset, err := network.BuildIPSet(
[]netip.Prefix{
netip.MustParsePrefix("10.0.0.0/8"),
netip.MustParsePrefix("2001:db8::/32"),
},
[]netip.Prefix{
netip.MustParsePrefix("10.4.0.0/16"),
})
require.NoError(t, err)

assert.Equal(t,
[]string{"10.0.0.0-10.3.255.255", "10.5.0.0-10.255.255.255", "2001:db8::-2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"},
xslices.Map(ipset.Ranges(), netipx.IPRange.String),
)
}

func TestSplitIPSet(t *testing.T) {
ipset, err := network.BuildIPSet(
[]netip.Prefix{
netip.MustParsePrefix("10.0.0.0/8"),
netip.MustParsePrefix("2001:db8::/32"),
},
[]netip.Prefix{
netip.MustParsePrefix("10.4.0.0/16"),
})
require.NoError(t, err)

v4, v6 := network.SplitIPSet(ipset)

assert.Equal(t,
[]string{"10.0.0.0-10.3.255.255", "10.5.0.0-10.255.255.255"},
xslices.Map(v4, netipx.IPRange.String),
)

assert.Equal(t,
[]string{"2001:db8::-2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"},
xslices.Map(v6, netipx.IPRange.String),
)
}
Loading

0 comments on commit e46e6a3

Please sign in to comment.