2023-03-16 10:00:08 +01:00
|
|
|
package iptables
|
|
|
|
|
|
|
|
import (
|
2023-05-29 16:00:18 +02:00
|
|
|
"fmt"
|
2023-03-16 10:00:08 +01:00
|
|
|
"net"
|
|
|
|
"testing"
|
2023-05-29 16:00:18 +02:00
|
|
|
"time"
|
2023-03-16 10:00:08 +01:00
|
|
|
|
|
|
|
"github.com/coreos/go-iptables/iptables"
|
2023-05-29 16:00:18 +02:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2023-03-16 10:00:08 +01:00
|
|
|
fw "github.com/netbirdio/netbird/client/firewall"
|
2023-06-07 15:24:27 +02:00
|
|
|
"github.com/netbirdio/netbird/iface"
|
2023-03-16 10:00:08 +01:00
|
|
|
)
|
|
|
|
|
2023-06-07 15:24:27 +02:00
|
|
|
// iFaceMapper defines subset methods of interface required for manager
|
|
|
|
type iFaceMock struct {
|
|
|
|
NameFunc func() string
|
|
|
|
AddressFunc func() iface.WGAddress
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *iFaceMock) Name() string {
|
|
|
|
if i.NameFunc != nil {
|
|
|
|
return i.NameFunc()
|
|
|
|
}
|
|
|
|
panic("NameFunc is not set")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *iFaceMock) Address() iface.WGAddress {
|
|
|
|
if i.AddressFunc != nil {
|
|
|
|
return i.AddressFunc()
|
|
|
|
}
|
|
|
|
panic("AddressFunc is not set")
|
|
|
|
}
|
|
|
|
|
2023-09-05 21:07:32 +02:00
|
|
|
func (i *iFaceMock) IsUserspaceBind() bool { return false }
|
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
func TestIptablesManager(t *testing.T) {
|
2023-03-16 10:00:08 +01:00
|
|
|
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
2023-05-29 16:00:18 +02:00
|
|
|
require.NoError(t, err)
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-06-07 15:24:27 +02:00
|
|
|
mock := &iFaceMock{
|
|
|
|
NameFunc: func() string {
|
|
|
|
return "lo"
|
|
|
|
},
|
|
|
|
AddressFunc: func() iface.WGAddress {
|
|
|
|
return iface.WGAddress{
|
|
|
|
IP: net.ParseIP("10.20.0.1"),
|
|
|
|
Network: &net.IPNet{
|
|
|
|
IP: net.ParseIP("10.20.0.0"),
|
|
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
// just check on the local interface
|
2023-09-05 21:07:32 +02:00
|
|
|
manager, err := Create(mock, true)
|
2023-05-29 16:00:18 +02:00
|
|
|
require.NoError(t, err)
|
2023-07-24 13:00:23 +02:00
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
|
|
|
defer func() {
|
2023-07-24 13:00:23 +02:00
|
|
|
err := manager.Reset()
|
|
|
|
require.NoError(t, err, "clear the manager state")
|
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
time.Sleep(time.Second)
|
|
|
|
}()
|
2023-03-16 10:00:08 +01:00
|
|
|
|
|
|
|
var rule1 fw.Rule
|
|
|
|
t.Run("add first rule", func(t *testing.T) {
|
|
|
|
ip := net.ParseIP("10.20.0.2")
|
2023-05-29 16:00:18 +02:00
|
|
|
port := &fw.Port{Values: []int{8080}}
|
2023-07-18 11:12:50 +02:00
|
|
|
rule1, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "", "accept HTTP traffic")
|
2023-05-29 16:00:18 +02:00
|
|
|
require.NoError(t, err, "failed to add rule")
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
checkRuleSpecs(t, ipv4Client, ChainOutputFilterName, true, rule1.(*Rule).specs...)
|
2023-03-16 10:00:08 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
var rule2 fw.Rule
|
|
|
|
t.Run("add second rule", func(t *testing.T) {
|
|
|
|
ip := net.ParseIP("10.20.0.3")
|
|
|
|
port := &fw.Port{
|
|
|
|
Values: []int{8043: 8046},
|
|
|
|
}
|
|
|
|
rule2, err = manager.AddFiltering(
|
2023-07-18 11:12:50 +02:00
|
|
|
ip, "tcp", port, nil, fw.RuleDirectionIN, fw.ActionAccept, "", "accept HTTPS traffic from ports range")
|
2023-05-29 16:00:18 +02:00
|
|
|
require.NoError(t, err, "failed to add rule")
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
checkRuleSpecs(t, ipv4Client, ChainInputFilterName, true, rule2.(*Rule).specs...)
|
2023-03-16 10:00:08 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("delete first rule", func(t *testing.T) {
|
2023-07-24 13:00:23 +02:00
|
|
|
err := manager.DeleteRule(rule1)
|
|
|
|
require.NoError(t, err, "failed to delete rule")
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
checkRuleSpecs(t, ipv4Client, ChainOutputFilterName, false, rule1.(*Rule).specs...)
|
2023-03-16 10:00:08 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("delete second rule", func(t *testing.T) {
|
2023-07-24 13:00:23 +02:00
|
|
|
err := manager.DeleteRule(rule2)
|
|
|
|
require.NoError(t, err, "failed to delete rule")
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-07-24 13:00:23 +02:00
|
|
|
require.Empty(t, manager.rulesets, "rulesets index after removed second rule must be empty")
|
2023-03-16 10:00:08 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("reset check", func(t *testing.T) {
|
|
|
|
// add second rule
|
|
|
|
ip := net.ParseIP("10.20.0.3")
|
2023-05-29 16:00:18 +02:00
|
|
|
port := &fw.Port{Values: []int{5353}}
|
2023-07-18 11:12:50 +02:00
|
|
|
_, err = manager.AddFiltering(ip, "udp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "", "accept Fake DNS traffic")
|
2023-05-29 16:00:18 +02:00
|
|
|
require.NoError(t, err, "failed to add rule")
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
err = manager.Reset()
|
|
|
|
require.NoError(t, err, "failed to reset")
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
ok, err := ipv4Client.ChainExists("filter", ChainInputFilterName)
|
|
|
|
require.NoError(t, err, "failed check chain exists")
|
2023-03-16 10:00:08 +01:00
|
|
|
|
|
|
|
if ok {
|
2023-05-29 16:00:18 +02:00
|
|
|
require.NoErrorf(t, err, "chain '%v' still exists after Reset", ChainInputFilterName)
|
2023-03-16 10:00:08 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-24 13:00:23 +02:00
|
|
|
func TestIptablesManagerIPSet(t *testing.T) {
|
|
|
|
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
mock := &iFaceMock{
|
|
|
|
NameFunc: func() string {
|
|
|
|
return "lo"
|
|
|
|
},
|
|
|
|
AddressFunc: func() iface.WGAddress {
|
|
|
|
return iface.WGAddress{
|
|
|
|
IP: net.ParseIP("10.20.0.1"),
|
|
|
|
Network: &net.IPNet{
|
|
|
|
IP: net.ParseIP("10.20.0.0"),
|
|
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// just check on the local interface
|
2023-09-05 21:07:32 +02:00
|
|
|
manager, err := Create(mock, true)
|
2023-07-24 13:00:23 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err := manager.Reset()
|
|
|
|
require.NoError(t, err, "clear the manager state")
|
|
|
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}()
|
|
|
|
|
|
|
|
var rule1 fw.Rule
|
|
|
|
t.Run("add first rule with set", func(t *testing.T) {
|
|
|
|
ip := net.ParseIP("10.20.0.2")
|
|
|
|
port := &fw.Port{Values: []int{8080}}
|
|
|
|
rule1, err = manager.AddFiltering(
|
|
|
|
ip, "tcp", nil, port, fw.RuleDirectionOUT,
|
|
|
|
fw.ActionAccept, "default", "accept HTTP traffic",
|
|
|
|
)
|
|
|
|
require.NoError(t, err, "failed to add rule")
|
|
|
|
|
|
|
|
checkRuleSpecs(t, ipv4Client, ChainOutputFilterName, true, rule1.(*Rule).specs...)
|
|
|
|
require.Equal(t, rule1.(*Rule).ipsetName, "default-dport", "ipset name must be set")
|
|
|
|
require.Equal(t, rule1.(*Rule).ip, "10.20.0.2", "ipset IP must be set")
|
|
|
|
})
|
|
|
|
|
|
|
|
var rule2 fw.Rule
|
|
|
|
t.Run("add second rule", func(t *testing.T) {
|
|
|
|
ip := net.ParseIP("10.20.0.3")
|
|
|
|
port := &fw.Port{
|
|
|
|
Values: []int{443},
|
|
|
|
}
|
|
|
|
rule2, err = manager.AddFiltering(
|
|
|
|
ip, "tcp", port, nil, fw.RuleDirectionIN, fw.ActionAccept,
|
|
|
|
"default", "accept HTTPS traffic from ports range",
|
|
|
|
)
|
|
|
|
require.NoError(t, err, "failed to add rule")
|
|
|
|
require.Equal(t, rule2.(*Rule).ipsetName, "default-sport", "ipset name must be set")
|
|
|
|
require.Equal(t, rule2.(*Rule).ip, "10.20.0.3", "ipset IP must be set")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("delete first rule", func(t *testing.T) {
|
|
|
|
err := manager.DeleteRule(rule1)
|
|
|
|
require.NoError(t, err, "failed to delete rule")
|
|
|
|
|
|
|
|
require.NotContains(t, manager.rulesets, rule1.(*Rule).ruleID, "rule must be removed form the ruleset index")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("delete second rule", func(t *testing.T) {
|
|
|
|
err := manager.DeleteRule(rule2)
|
|
|
|
require.NoError(t, err, "failed to delete rule")
|
|
|
|
|
|
|
|
require.Empty(t, manager.rulesets, "rulesets index after removed second rule must be empty")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("reset check", func(t *testing.T) {
|
|
|
|
err = manager.Reset()
|
|
|
|
require.NoError(t, err, "failed to reset")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, chainName string, mustExists bool, rulespec ...string) {
|
2023-11-10 16:33:13 +01:00
|
|
|
t.Helper()
|
2023-05-29 16:00:18 +02:00
|
|
|
exists, err := ipv4Client.Exists("filter", chainName, rulespec...)
|
|
|
|
require.NoError(t, err, "failed to check rule")
|
|
|
|
require.Falsef(t, !exists && mustExists, "rule '%v' does not exist", rulespec)
|
|
|
|
require.Falsef(t, exists && !mustExists, "rule '%v' exist", rulespec)
|
|
|
|
}
|
2023-03-16 10:00:08 +01:00
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
func TestIptablesCreatePerformance(t *testing.T) {
|
2023-06-07 15:24:27 +02:00
|
|
|
mock := &iFaceMock{
|
|
|
|
NameFunc: func() string {
|
|
|
|
return "lo"
|
|
|
|
},
|
|
|
|
AddressFunc: func() iface.WGAddress {
|
|
|
|
return iface.WGAddress{
|
|
|
|
IP: net.ParseIP("10.20.0.1"),
|
|
|
|
Network: &net.IPNet{
|
|
|
|
IP: net.ParseIP("10.20.0.0"),
|
|
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
|
|
|
|
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
|
|
|
|
// just check on the local interface
|
2023-09-05 21:07:32 +02:00
|
|
|
manager, err := Create(mock, true)
|
2023-05-29 16:00:18 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
|
|
|
defer func() {
|
2023-07-24 13:00:23 +02:00
|
|
|
err := manager.Reset()
|
|
|
|
require.NoError(t, err, "clear the manager state")
|
|
|
|
|
2023-05-29 16:00:18 +02:00
|
|
|
time.Sleep(time.Second)
|
|
|
|
}()
|
|
|
|
|
|
|
|
_, err = manager.client(net.ParseIP("10.20.0.100"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
ip := net.ParseIP("10.20.0.100")
|
|
|
|
start := time.Now()
|
|
|
|
for i := 0; i < testMax; i++ {
|
|
|
|
port := &fw.Port{Values: []int{1000 + i}}
|
|
|
|
if i%2 == 0 {
|
2023-07-18 11:12:50 +02:00
|
|
|
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "", "accept HTTP traffic")
|
2023-05-29 16:00:18 +02:00
|
|
|
} else {
|
2023-07-18 11:12:50 +02:00
|
|
|
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionIN, fw.ActionAccept, "", "accept HTTP traffic")
|
2023-05-29 16:00:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, err, "failed to add rule")
|
|
|
|
}
|
|
|
|
t.Logf("execution avg per rule: %s", time.Since(start)/time.Duration(testMax))
|
|
|
|
})
|
2023-03-16 10:00:08 +01:00
|
|
|
}
|
|
|
|
}
|