mirror of
https://github.com/qdm12/gluetun.git
synced 2026-08-11 14:52:56 +02:00
ai generated tests
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_AcceptInputThroughInterface(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
fw := New(nil)
|
||||
|
||||
err := fw.AcceptInputThroughInterface(ctx, "tun0")
|
||||
// Verify no panic; may fail if not running as root
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), "creating nftables connection")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AcceptInputToPort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
intf string
|
||||
port uint16
|
||||
remove bool
|
||||
}{
|
||||
"add rule with interface": {
|
||||
intf: "tun0",
|
||||
port: 8080,
|
||||
remove: false,
|
||||
},
|
||||
"add rule without interface": {
|
||||
intf: "",
|
||||
port: 443,
|
||||
remove: false,
|
||||
},
|
||||
"add rule with star interface": {
|
||||
intf: "*",
|
||||
port: 53,
|
||||
remove: false,
|
||||
},
|
||||
"remove rule": {
|
||||
intf: "tun0",
|
||||
port: 8080,
|
||||
remove: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
fw := New(nil)
|
||||
|
||||
err := fw.AcceptInputToPort(ctx, tc.intf, tc.port, tc.remove)
|
||||
// May fail if not running as root
|
||||
if err != nil && !tc.remove {
|
||||
assert.Contains(t, err.Error(), "creating nftables connection")
|
||||
} else if err != nil && tc.remove {
|
||||
// For remove, the rule won't exist, so expect error
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AcceptInputToPort_ExpressionStructure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Verify the expression structure for AcceptInputToPort
|
||||
conn, err := nftables.New()
|
||||
require.NoError(t, err)
|
||||
table, inputChain, _, _ := setupFilterWithBaseChains(conn)
|
||||
|
||||
const port = 80
|
||||
portBytes := []byte{byte(port >> 8), byte(port)}
|
||||
const tcp uint8 = 6
|
||||
|
||||
// Build expressions for a rule with interface filter
|
||||
exprs := []expr.Any{
|
||||
// Interface match
|
||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte("tun0\x00")},
|
||||
// Protocol match (TCP)
|
||||
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1},
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{tcp}},
|
||||
// Destination port match
|
||||
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2},
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: portBytes},
|
||||
&expr.Verdict{Kind: expr.VerdictAccept},
|
||||
}
|
||||
|
||||
rule := &nftables.Rule{
|
||||
Table: table,
|
||||
Chain: inputChain,
|
||||
Exprs: exprs,
|
||||
}
|
||||
|
||||
require.NotNil(t, rule)
|
||||
assert.Equal(t, "filter", rule.Table.Name)
|
||||
assert.Equal(t, "input", rule.Chain.Name)
|
||||
assert.Len(t, rule.Exprs, 7)
|
||||
}
|
||||
|
||||
func Test_AcceptInputToSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testCases := map[string]struct {
|
||||
intf string
|
||||
subnet netip.Prefix
|
||||
}{
|
||||
"IPv4 subnet with interface": {
|
||||
intf: "tun0",
|
||||
subnet: mustParsePrefix("192.168.1.0/24"),
|
||||
},
|
||||
"IPv4 subnet without interface": {
|
||||
intf: "",
|
||||
subnet: mustParsePrefix("10.0.0.0/8"),
|
||||
},
|
||||
"IPv6 subnet with interface": {
|
||||
intf: "tun0",
|
||||
subnet: mustParsePrefix("fd00::/64"),
|
||||
},
|
||||
"IPv6 subnet without interface": {
|
||||
intf: "",
|
||||
subnet: mustParsePrefix("fe80::/10"),
|
||||
},
|
||||
"single IPv4 host": {
|
||||
intf: "tun0",
|
||||
subnet: mustParsePrefix("192.168.1.1/32"),
|
||||
},
|
||||
"single IPv6 host": {
|
||||
intf: "tun0",
|
||||
subnet: mustParsePrefix("2001:db8::1/128"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fw := New(nil)
|
||||
|
||||
err := fw.AcceptInputToSubnet(ctx, tc.intf, tc.subnet)
|
||||
// May fail if not running as root
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), "creating nftables connection")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AcceptInputToSubnet_PayloadOffset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Verify correct payload offset for IPv4 vs IPv6
|
||||
_, err := nftables.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
// IPv4: destination address at offset 16.
|
||||
// IPv4 header layout: version(1) + IHL(1) + tos(1) + total length(2) +
|
||||
// ID(2) + flags(2) + TTL(1) + protocol(1) + checksum(2) + src(4) + dst(4).
|
||||
// So dst starts at offset 16.
|
||||
v4Subnet := mustParsePrefix("192.168.1.0/24")
|
||||
v4Exprs := buildInputSubnetExprs("", v4Subnet)
|
||||
v4Payload, ok := v4Exprs[len(v4Exprs)-3].(*expr.Payload)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, uint32(16), v4Payload.Offset)
|
||||
|
||||
// IPv6: destination address at offset 24.
|
||||
// IPv6 header layout: version(1) + traffic class(1) + flow label(2) +
|
||||
// payload length(2) + next header(1) + hop limit(1) + src(16) + dst(16).
|
||||
// So dst starts at offset 8 + 16 = 24.
|
||||
v6Subnet := mustParsePrefix("fd00::/64")
|
||||
v6Exprs := buildInputSubnetExprs("", v6Subnet)
|
||||
v6Payload, ok := v6Exprs[len(v6Exprs)-3].(*expr.Payload)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, uint32(24), v6Payload.Offset)
|
||||
|
||||
_ = v4Exprs
|
||||
_ = v6Exprs
|
||||
_ = err
|
||||
}
|
||||
|
||||
func buildInputSubnetExprs(intf string, subnet netip.Prefix) []expr.Any {
|
||||
const maxExprsLen = 5
|
||||
exprs := make([]expr.Any, 0, maxExprsLen)
|
||||
|
||||
if intf != "" && intf != "*" {
|
||||
exprs = append(exprs,
|
||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte(intf + "\x00")},
|
||||
)
|
||||
}
|
||||
|
||||
var payloadOffset uint32
|
||||
if subnet.Addr().Is4() {
|
||||
payloadOffset = 16
|
||||
} else {
|
||||
payloadOffset = 24
|
||||
}
|
||||
|
||||
exprs = append(exprs,
|
||||
&expr.Payload{
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: payloadOffset,
|
||||
Len: uint32(len(subnet.Addr().AsSlice())), //nolint:gosec // address length is at most 16 bytes
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: subnet.Addr().AsSlice(),
|
||||
},
|
||||
&expr.Verdict{Kind: expr.VerdictAccept},
|
||||
)
|
||||
|
||||
return exprs
|
||||
}
|
||||
|
||||
func mustParsePrefix(s string) netip.Prefix {
|
||||
p, err := netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
Reference in New Issue
Block a user