mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-19 16:56:39 +00:00
280 lines
8.3 KiB
Go
280 lines
8.3 KiB
Go
package internal
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/netbirdio/netbird/client/inspect"
|
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
|
)
|
|
|
|
func TestToProxyConfig_Basic(t *testing.T) {
|
|
cfg := &mgmProto.TransparentProxyConfig{
|
|
Enabled: true,
|
|
Mode: mgmProto.TransparentProxyMode_TP_MODE_BUILTIN,
|
|
DefaultAction: mgmProto.TransparentProxyAction_TP_ACTION_ALLOW,
|
|
RedirectSources: []string{
|
|
"10.0.0.0/24",
|
|
"192.168.1.0/24",
|
|
},
|
|
RedirectPorts: []uint32{80, 443},
|
|
Rules: []*mgmProto.TransparentProxyRule{
|
|
{
|
|
Id: "block-evil",
|
|
Domains: []string{"*.evil.com", "malware.example.com"},
|
|
Action: mgmProto.TransparentProxyAction_TP_ACTION_BLOCK,
|
|
Priority: 1,
|
|
},
|
|
{
|
|
Id: "inspect-internal",
|
|
Domains: []string{"*.internal.corp"},
|
|
Networks: []string{"10.1.0.0/16"},
|
|
Ports: []uint32{443, 8443},
|
|
Action: mgmProto.TransparentProxyAction_TP_ACTION_INSPECT,
|
|
Priority: 10,
|
|
},
|
|
},
|
|
ListenPort: 8443,
|
|
}
|
|
|
|
config, err := toProxyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, config.Enabled)
|
|
assert.Equal(t, inspect.ModeBuiltin, config.Mode)
|
|
assert.Equal(t, inspect.ActionAllow, config.DefaultAction)
|
|
|
|
require.Len(t, config.RedirectSources, 2)
|
|
assert.Equal(t, "10.0.0.0/24", config.RedirectSources[0].String())
|
|
assert.Equal(t, "192.168.1.0/24", config.RedirectSources[1].String())
|
|
|
|
require.Len(t, config.RedirectPorts, 2)
|
|
assert.Equal(t, uint16(80), config.RedirectPorts[0])
|
|
assert.Equal(t, uint16(443), config.RedirectPorts[1])
|
|
|
|
require.Len(t, config.Rules, 2)
|
|
|
|
// Rule 1: block evil domains
|
|
assert.Equal(t, "block-evil", string(config.Rules[0].ID))
|
|
assert.Equal(t, inspect.ActionBlock, config.Rules[0].Action)
|
|
assert.Equal(t, 1, config.Rules[0].Priority)
|
|
require.Len(t, config.Rules[0].Domains, 2)
|
|
assert.Equal(t, "*.evil.com", config.Rules[0].Domains[0].PunycodeString())
|
|
assert.Equal(t, "malware.example.com", config.Rules[0].Domains[1].PunycodeString())
|
|
|
|
// Rule 2: inspect internal
|
|
assert.Equal(t, "inspect-internal", string(config.Rules[1].ID))
|
|
assert.Equal(t, inspect.ActionInspect, config.Rules[1].Action)
|
|
assert.Equal(t, 10, config.Rules[1].Priority)
|
|
require.Len(t, config.Rules[1].Networks, 1)
|
|
assert.Equal(t, "10.1.0.0/16", config.Rules[1].Networks[0].String())
|
|
require.Len(t, config.Rules[1].Ports, 2)
|
|
|
|
// Listen address
|
|
assert.True(t, config.ListenAddr.IsValid())
|
|
assert.Equal(t, uint16(8443), config.ListenAddr.Port())
|
|
}
|
|
|
|
func TestToProxyConfig_ExternalMode(t *testing.T) {
|
|
cfg := &mgmProto.TransparentProxyConfig{
|
|
Enabled: true,
|
|
Mode: mgmProto.TransparentProxyMode_TP_MODE_EXTERNAL,
|
|
ExternalProxyUrl: "http://proxy.corp:8080",
|
|
DefaultAction: mgmProto.TransparentProxyAction_TP_ACTION_BLOCK,
|
|
}
|
|
|
|
config, err := toProxyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, inspect.ModeExternal, config.Mode)
|
|
assert.Equal(t, inspect.ActionBlock, config.DefaultAction)
|
|
require.NotNil(t, config.ExternalURL)
|
|
assert.Equal(t, "http", config.ExternalURL.Scheme)
|
|
assert.Equal(t, "proxy.corp:8080", config.ExternalURL.Host)
|
|
}
|
|
|
|
func TestToProxyConfig_ICAP(t *testing.T) {
|
|
cfg := &mgmProto.TransparentProxyConfig{
|
|
Enabled: true,
|
|
Icap: &mgmProto.TransparentProxyICAPConfig{
|
|
ReqmodUrl: "icap://icap-server:1344/reqmod",
|
|
RespmodUrl: "icap://icap-server:1344/respmod",
|
|
MaxConnections: 16,
|
|
},
|
|
}
|
|
|
|
config, err := toProxyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, config.ICAP)
|
|
assert.Equal(t, "icap", config.ICAP.ReqModURL.Scheme)
|
|
assert.Equal(t, "icap-server:1344", config.ICAP.ReqModURL.Host)
|
|
assert.Equal(t, "/reqmod", config.ICAP.ReqModURL.Path)
|
|
assert.Equal(t, "/respmod", config.ICAP.RespModURL.Path)
|
|
assert.Equal(t, 16, config.ICAP.MaxConnections)
|
|
}
|
|
|
|
func TestToProxyConfig_Empty(t *testing.T) {
|
|
cfg := &mgmProto.TransparentProxyConfig{
|
|
Enabled: true,
|
|
}
|
|
|
|
config, err := toProxyConfig(cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, config.Enabled)
|
|
assert.Equal(t, inspect.ModeBuiltin, config.Mode)
|
|
assert.Equal(t, inspect.ActionAllow, config.DefaultAction)
|
|
assert.Empty(t, config.RedirectSources)
|
|
assert.Empty(t, config.RedirectPorts)
|
|
assert.Empty(t, config.Rules)
|
|
assert.Nil(t, config.ICAP)
|
|
assert.Nil(t, config.TLS)
|
|
assert.False(t, config.ListenAddr.IsValid())
|
|
}
|
|
|
|
func TestToProxyConfig_InvalidSource(t *testing.T) {
|
|
cfg := &mgmProto.TransparentProxyConfig{
|
|
Enabled: true,
|
|
RedirectSources: []string{"not-a-cidr"},
|
|
}
|
|
|
|
_, err := toProxyConfig(cfg)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse redirect source")
|
|
}
|
|
|
|
func TestToProxyConfig_InvalidNetwork(t *testing.T) {
|
|
cfg := &mgmProto.TransparentProxyConfig{
|
|
Enabled: true,
|
|
Rules: []*mgmProto.TransparentProxyRule{
|
|
{
|
|
Id: "bad",
|
|
Networks: []string{"not-a-cidr"},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := toProxyConfig(cfg)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse network")
|
|
}
|
|
|
|
func TestToProxyAction(t *testing.T) {
|
|
assert.Equal(t, inspect.ActionAllow, toProxyAction(mgmProto.TransparentProxyAction_TP_ACTION_ALLOW))
|
|
assert.Equal(t, inspect.ActionBlock, toProxyAction(mgmProto.TransparentProxyAction_TP_ACTION_BLOCK))
|
|
assert.Equal(t, inspect.ActionInspect, toProxyAction(mgmProto.TransparentProxyAction_TP_ACTION_INSPECT))
|
|
// Unknown defaults to allow
|
|
assert.Equal(t, inspect.ActionAllow, toProxyAction(99))
|
|
}
|
|
|
|
func TestParseUDPPacket_IPv4(t *testing.T) {
|
|
// Build a minimal IPv4/UDP packet: 20-byte IPv4 header + 8-byte UDP header + payload
|
|
packet := make([]byte, 20+8+4)
|
|
|
|
// IPv4 header: version=4, IHL=5 (20 bytes)
|
|
packet[0] = 0x45
|
|
// Protocol = UDP (17)
|
|
packet[9] = 17
|
|
// Source IP: 10.0.0.1
|
|
packet[12], packet[13], packet[14], packet[15] = 10, 0, 0, 1
|
|
// Dest IP: 192.168.1.1
|
|
packet[16], packet[17], packet[18], packet[19] = 192, 168, 1, 1
|
|
// UDP source port: 54321 (0xD431)
|
|
packet[20] = 0xD4
|
|
packet[21] = 0x31
|
|
// UDP dest port: 443 (0x01BB)
|
|
packet[22] = 0x01
|
|
packet[23] = 0xBB
|
|
// Payload
|
|
packet[28] = 0xDE
|
|
packet[29] = 0xAD
|
|
packet[30] = 0xBE
|
|
packet[31] = 0xEF
|
|
|
|
srcIP, dstIP, dstPort, payload, ok := parseUDPPacket(packet)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "10.0.0.1", srcIP.String())
|
|
assert.Equal(t, "192.168.1.1", dstIP.String())
|
|
assert.Equal(t, uint16(443), dstPort)
|
|
assert.Equal(t, []byte{0xDE, 0xAD, 0xBE, 0xEF}, payload)
|
|
}
|
|
|
|
func TestParseUDPPacket_IPv6(t *testing.T) {
|
|
// Build a minimal IPv6/UDP packet: 40-byte IPv6 header + 8-byte UDP header + payload
|
|
packet := make([]byte, 40+8+4)
|
|
|
|
// Version = 6 (0x60 in high nibble)
|
|
packet[0] = 0x60
|
|
// Payload length: 8 (UDP header) + 4 (payload)
|
|
packet[4] = 0
|
|
packet[5] = 12
|
|
// Next header: UDP (17)
|
|
packet[6] = 17
|
|
// Source: 2001:db8::1
|
|
packet[8] = 0x20
|
|
packet[9] = 0x01
|
|
packet[10] = 0x0d
|
|
packet[11] = 0xb8
|
|
packet[23] = 0x01
|
|
// Dest: 2001:db8::2
|
|
packet[24] = 0x20
|
|
packet[25] = 0x01
|
|
packet[26] = 0x0d
|
|
packet[27] = 0xb8
|
|
packet[39] = 0x02
|
|
// UDP source port: 54321 (0xD431)
|
|
packet[40] = 0xD4
|
|
packet[41] = 0x31
|
|
// UDP dest port: 443 (0x01BB)
|
|
packet[42] = 0x01
|
|
packet[43] = 0xBB
|
|
// Payload
|
|
packet[48] = 0xCA
|
|
packet[49] = 0xFE
|
|
packet[50] = 0xBA
|
|
packet[51] = 0xBE
|
|
|
|
srcIP, dstIP, dstPort, payload, ok := parseUDPPacket(packet)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "2001:db8::1", srcIP.String())
|
|
assert.Equal(t, "2001:db8::2", dstIP.String())
|
|
assert.Equal(t, uint16(443), dstPort)
|
|
assert.Equal(t, []byte{0xCA, 0xFE, 0xBA, 0xBE}, payload)
|
|
}
|
|
|
|
func TestParseUDPPacket_TooShort(t *testing.T) {
|
|
_, _, _, _, ok := parseUDPPacket(nil)
|
|
assert.False(t, ok)
|
|
|
|
_, _, _, _, ok = parseUDPPacket([]byte{0x45, 0x00})
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
func TestParseUDPPacket_IPv6ExtensionHeader(t *testing.T) {
|
|
// IPv6 with next header != UDP should be rejected
|
|
packet := make([]byte, 48)
|
|
packet[0] = 0x60
|
|
packet[6] = 6 // TCP, not UDP
|
|
_, _, _, _, ok := parseUDPPacket(packet)
|
|
assert.False(t, ok, "should reject IPv6 packets with non-UDP next header")
|
|
}
|
|
|
|
func TestParseUDPPacket_IPv4MappedIPv6(t *testing.T) {
|
|
// IPv4 packet with normal addresses should Unmap correctly
|
|
packet := make([]byte, 28)
|
|
packet[0] = 0x45
|
|
packet[9] = 17
|
|
packet[12], packet[13], packet[14], packet[15] = 127, 0, 0, 1
|
|
packet[16], packet[17], packet[18], packet[19] = 10, 0, 0, 1
|
|
packet[22] = 0x01
|
|
packet[23] = 0xBB
|
|
|
|
srcIP, dstIP, _, _, ok := parseUDPPacket(packet)
|
|
require.True(t, ok)
|
|
assert.True(t, srcIP.Is4(), "should be plain IPv4, not mapped")
|
|
assert.True(t, dstIP.Is4(), "should be plain IPv4, not mapped")
|
|
}
|