Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions client/firewall/nftables/manager_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,97 @@ func TestNftablesManagerCompatibilityWithIptables(t *testing.T) {
verifyIptablesOutput(t, stdout, stderr)
}

func TestNftablesManagerCompatibilityWithIptablesFor6kPrefixes(t *testing.T) {
if check() != NFTABLES {
t.Skip("nftables not supported on this system")
}

if _, err := exec.LookPath("iptables-save"); err != nil {
t.Skipf("iptables-save not available on this system: %v", err)
}

// First ensure iptables-nft tables exist by running iptables-save
stdout, stderr := runIptablesSave(t)
verifyIptablesOutput(t, stdout, stderr)

manager, err := Create(ifaceMock, iface.DefaultMTU)
require.NoError(t, err, "failed to create manager")
require.NoError(t, manager.Init(nil))

t.Cleanup(func() {
err := manager.Close(nil)
require.NoError(t, err, "failed to reset manager state")

// Verify iptables output after reset
stdout, stderr := runIptablesSave(t)
verifyIptablesOutput(t, stdout, stderr)
})

const octet2Count = 25
const octet3Count = 255
prefixes := make([]netip.Prefix, 0, (octet2Count-1)*(octet3Count-1))
for i := 1; i < octet2Count; i++ {
for j := 1; j < octet3Count; j++ {
addr := netip.AddrFrom4([4]byte{192, byte(j), byte(i), 0})
prefixes = append(prefixes, netip.PrefixFrom(addr, 24))
}
}
_, err = manager.AddRouteFiltering(
nil,
prefixes,
fw.Network{Prefix: netip.MustParsePrefix("10.2.0.0/24")},
fw.ProtocolTCP,
nil,
&fw.Port{Values: []uint16{443}},
fw.ActionAccept,
)
require.NoError(t, err, "failed to add route filtering rule")

stdout, stderr = runIptablesSave(t)
verifyIptablesOutput(t, stdout, stderr)
}

func TestNftablesManagerCompatibilityWithIptablesForEmptyPrefixes(t *testing.T) {
if check() != NFTABLES {
t.Skip("nftables not supported on this system")
}

if _, err := exec.LookPath("iptables-save"); err != nil {
t.Skipf("iptables-save not available on this system: %v", err)
}

// First ensure iptables-nft tables exist by running iptables-save
stdout, stderr := runIptablesSave(t)
verifyIptablesOutput(t, stdout, stderr)

manager, err := Create(ifaceMock, iface.DefaultMTU)
require.NoError(t, err, "failed to create manager")
require.NoError(t, manager.Init(nil))

t.Cleanup(func() {
err := manager.Close(nil)
require.NoError(t, err, "failed to reset manager state")

// Verify iptables output after reset
stdout, stderr := runIptablesSave(t)
verifyIptablesOutput(t, stdout, stderr)
})

_, err = manager.AddRouteFiltering(
nil,
[]netip.Prefix{},
fw.Network{Prefix: netip.MustParsePrefix("10.2.0.0/24")},
fw.ProtocolTCP,
nil,
&fw.Port{Values: []uint16{443}},
fw.ActionAccept,
)
require.NoError(t, err, "failed to add route filtering rule")

stdout, stderr = runIptablesSave(t)
verifyIptablesOutput(t, stdout, stderr)
}

func compareExprsIgnoringCounters(t *testing.T, got, want []expr.Any) {
t.Helper()
require.Equal(t, len(got), len(want), "expression count mismatch")
Expand Down
61 changes: 42 additions & 19 deletions client/firewall/nftables/router_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ const (

// ipTCPHeaderMinSize represents minimum IP (20) + TCP (20) header size for MSS calculation
ipTCPHeaderMinSize = 40
)

const refreshRulesMapError = "refresh rules map: %w"
// maxPrefixesSet 3277 prefixes start to fail, taking some margin
maxPrefixesSet = 3000
refreshRulesMapError = "refresh rules map: %w"
)

var (
errFilterTableNotFound = fmt.Errorf("'filter' table not found")
Expand Down Expand Up @@ -346,26 +348,11 @@ func (r *router) AddRouteFiltering(
}

chain := r.chains[chainNameRoutingFw]
var exprs []expr.Any

var source firewall.Network
switch {
case len(sources) == 1 && sources[0].Bits() == 0:
// If it's 0.0.0.0/0, we don't need to add any source matching
case len(sources) == 1:
// If there's only one source, we can use it directly
source.Prefix = sources[0]
default:
// If there are multiple sources, use a set
source.Set = firewall.NewPrefixSet(sources)
}

sourceExp, err := r.applyNetwork(source, sources, true)
exprs, err := r.applySources(sources)
if err != nil {
return nil, fmt.Errorf("apply source: %w", err)
return nil, fmt.Errorf("apply sources (%d): %w", len(sources), err)
}
exprs = append(exprs, sourceExp...)

destExp, err := r.applyNetwork(destination, nil, false)
if err != nil {
return nil, fmt.Errorf("apply destination: %w", err)
Expand Down Expand Up @@ -425,6 +412,42 @@ func (r *router) AddRouteFiltering(
return ruleKey, nil
}

func (r *router) applySources(sources []netip.Prefix) ([]expr.Any, error) {
var source firewall.Network
if len(sources) == 0 {
return nil, nil
}
if len(sources) == 1 {
if sources[0].Bits() == 0 {
// If it's 0.0.0.0/0, we don't need to add any source matching
return nil, nil
} else { // If there's only one source, we can use it directly
source.Prefix = sources[0]
}
return r.applyNetwork(source, sources, true)
}
// If there are multiple sources, use a set
var exprs []expr.Any

var subEnd int
for subStart := 0; subStart < len(sources); subStart += maxPrefixesSet {
subEnd += maxPrefixesSet
if subEnd > len(sources) {
subEnd = len(sources)
}
subSources := sources[subStart:subEnd]
source.Set = firewall.NewPrefixSet(subSources)

sourceExp, err := r.applyNetwork(source, subSources, true)
if err != nil {
return nil, fmt.Errorf("apply source: %w (prefixes %d)", err, len(subSources))
}
exprs = append(exprs, sourceExp...)
}

return exprs, nil
}

func (r *router) getIpSet(set firewall.Set, prefixes []netip.Prefix, isSource bool) ([]expr.Any, error) {
ref, err := r.ipsetCounter.Increment(set.HashedName(), setInput{
set: set,
Expand Down