safing-portmaster/profile/endpoints/endpoints_test.go
2023-09-06 11:07:31 +02:00

432 lines
9.6 KiB
Go

package endpoints
import (
"context"
"net"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/safing/portmaster/core/pmtesting"
"github.com/safing/portmaster/intel"
)
func TestMain(m *testing.M) {
pmtesting.TestMain(m, intel.Module)
}
func testEndpointMatch(t *testing.T, ep Endpoint, entity *intel.Entity, expectedResult EPResult) {
t.Helper()
result, _ := ep.Matches(context.TODO(), entity)
if result != expectedResult {
t.Errorf(
"line %d: unexpected result for endpoint %s and entity %+v: result=%s, expected=%s",
getLineNumberOfCaller(1),
ep,
entity,
result,
expectedResult,
)
}
}
func testFormat(t *testing.T, endpoint string, shouldSucceed bool) {
t.Helper()
_, err := parseEndpoint(endpoint)
if shouldSucceed {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
}
func TestEndpointFormat(t *testing.T) {
t.Parallel()
testFormat(t, "+ .", false)
testFormat(t, "+ .at", true)
testFormat(t, "+ .at.", true)
testFormat(t, "+ 1.at", true)
testFormat(t, "+ 1.at.", true)
testFormat(t, "+ 1.f.ix.de.", true)
testFormat(t, "+ *contains*", true)
testFormat(t, "+ *has.suffix", true)
testFormat(t, "+ *.has.suffix", true)
testFormat(t, "+ *has.prefix*", true)
testFormat(t, "+ *has.prefix.*", true)
testFormat(t, "+ .sub.and.prefix.*", false)
testFormat(t, "+ *.sub..and.prefix.*", false)
}
func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO
t.Parallel()
// ANY
ep, err := parseEndpoint("+ *")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
// DOMAIN
// wildcard domains
ep, err = parseEndpoint("+ *example.com")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc-example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc-example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
ep, err = parseEndpoint("+ *.example.com")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc-example.com.",
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc-example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), NoMatch)
ep, err = parseEndpoint("+ .example.com")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc-example.com.",
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc-example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), NoMatch)
ep, err = parseEndpoint("+ example.*")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), NoMatch)
ep, err = parseEndpoint("+ *.exampl*")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "abc.example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
ep, err = parseEndpoint("+ *.com.")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.org.",
}).Init(0), NoMatch)
// protocol
ep, err = parseEndpoint("+ example.com UDP")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 17,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), NoMatch)
// ports
ep, err = parseEndpoint("+ example.com 17/442-444")
if err != nil {
t.Fatal(err)
}
entity := (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 17,
Port: 441,
}).Init(0)
testEndpointMatch(t, ep, entity, NoMatch)
entity.Port = 442
entity.Init(0)
testEndpointMatch(t, ep, entity, Permitted)
entity.Port = 443
entity.Init(0)
testEndpointMatch(t, ep, entity, Permitted)
entity.Port = 444
entity.Init(0)
testEndpointMatch(t, ep, entity, Permitted)
entity.Port = 445
entity.Init(0)
testEndpointMatch(t, ep, entity, NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), NoMatch)
// IP
ep, err = parseEndpoint("+ 10.2.3.4")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.4"),
Protocol: 17,
Port: 443,
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "",
IP: net.ParseIP("10.2.3.3"),
Protocol: 6,
Port: 443,
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
IP: net.ParseIP("10.2.3.5"),
Protocol: 17,
Port: 443,
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
Domain: "example.com.",
}).Init(0), NoMatch)
// IP Range
ep, err = parseEndpoint("+ 10.2.3.0/24")
if err != nil {
t.Fatal(err)
}
testEndpointMatch(t, ep, (&intel.Entity{
IP: net.ParseIP("10.2.2.4"),
}).Init(0), NoMatch)
testEndpointMatch(t, ep, (&intel.Entity{
IP: net.ParseIP("10.2.3.4"),
}).Init(0), Permitted)
testEndpointMatch(t, ep, (&intel.Entity{
IP: net.ParseIP("10.2.4.4"),
}).Init(0), NoMatch)
// Skip test that need the geoip database in CI.
if !testing.Short() {
// ASN
ep, err = parseEndpoint("+ AS15169")
if err != nil {
t.Fatal(err)
}
entity = (&intel.Entity{IP: net.IPv4(8, 8, 8, 8)}).Init(0)
testEndpointMatch(t, ep, entity, Permitted)
entity = (&intel.Entity{IP: net.IPv4(1, 1, 1, 1)}).Init(0)
testEndpointMatch(t, ep, entity, NoMatch)
// Country
ep, err = parseEndpoint("+ AT")
if err != nil {
t.Fatal(err)
}
entity = (&intel.Entity{IP: net.IPv4(194, 232, 104, 1)}).Init(0) // orf.at
testEndpointMatch(t, ep, entity, Permitted)
entity = (&intel.Entity{IP: net.IPv4(151, 101, 1, 164)}).Init(0) // nytimes.com
testEndpointMatch(t, ep, entity, NoMatch)
}
// Scope
ep, err = parseEndpoint("+ Localhost,LAN")
if err != nil {
t.Fatal(err)
}
entity = (&intel.Entity{IP: net.IPv4(192, 168, 0, 1)}).Init(0)
testEndpointMatch(t, ep, entity, Permitted)
entity = (&intel.Entity{IP: net.IPv4(151, 101, 1, 164)}).Init(0) // nytimes.com
testEndpointMatch(t, ep, entity, NoMatch)
// Port with protocol wildcard
ep, err = parseEndpoint("+ * */443")
if err != nil {
t.Fatal(err)
}
entity = &intel.Entity{
Domain: "",
IP: net.ParseIP("10.2.3.4"),
Protocol: 6,
Port: 443,
}
entity.Init(0)
testEndpointMatch(t, ep, entity, Permitted)
// Lists
// Skip test that need the filter lists in CI.
if !testing.Short() {
_, err = parseEndpoint("+ L:A,B,C")
if err != nil {
t.Fatal(err)
}
}
// TODO: write test for lists matcher
}
func getLineNumberOfCaller(levels int) int {
_, _, line, _ := runtime.Caller(levels + 1) //nolint:dogsled
return line
}