mirror of
https://github.com/juanfont/headscale
synced 2026-04-25 17:15:33 +02:00
matcher: clarify DestsIsTheInternet single-family semantics
DestsIsTheInternet now reports the internet when either family's /0 is contained (0.0.0.0/0 or ::/0), matching what operators expect when they write the /0 directly. Also documents MatchFromStrings fail-open. Updates #3157
This commit is contained in:
@@ -53,6 +53,11 @@ func MatchFromFilterRule(rule tailcfg.FilterRule) Match {
|
|||||||
return MatchFromStrings(rule.SrcIPs, dests)
|
return MatchFromStrings(rule.SrcIPs, dests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchFromStrings builds a Match from raw source and destination
|
||||||
|
// strings. Unparseable entries are silently dropped (fail-open): the
|
||||||
|
// resulting Match is narrower than the input described, but never
|
||||||
|
// wider. Callers that need strict validation should pre-validate
|
||||||
|
// their inputs via util.ParseIPSet.
|
||||||
func MatchFromStrings(sources, destinations []string) Match {
|
func MatchFromStrings(sources, destinations []string) Match {
|
||||||
srcs := new(netipx.IPSetBuilder)
|
srcs := new(netipx.IPSetBuilder)
|
||||||
dests := new(netipx.IPSetBuilder)
|
dests := new(netipx.IPSetBuilder)
|
||||||
@@ -96,18 +101,20 @@ func (m *Match) DestsOverlapsPrefixes(prefixes ...netip.Prefix) bool {
|
|||||||
return slices.ContainsFunc(prefixes, m.dests.OverlapsPrefix)
|
return slices.ContainsFunc(prefixes, m.dests.OverlapsPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestsIsTheInternet reports if the destination contains "the internet"
|
// DestsIsTheInternet reports whether the destination covers "the
|
||||||
// which is a IPSet that represents "autogroup:internet" and is special
|
// internet" — the set represented by autogroup:internet, special-cased
|
||||||
// cased for exit nodes.
|
// for exit nodes. Returns true if either family's /0 is contained
|
||||||
// This checks if dests is a superset of TheInternet(), which handles
|
// (0.0.0.0/0 or ::/0), or if dests is a superset of TheInternet(). A
|
||||||
// merged filter rules where TheInternet is combined with other destinations.
|
// single-family /0 counts because operators may write it directly and
|
||||||
|
// it still denotes the whole internet for that family.
|
||||||
func (m *Match) DestsIsTheInternet() bool {
|
func (m *Match) DestsIsTheInternet() bool {
|
||||||
if m.dests.ContainsPrefix(tsaddr.AllIPv4()) ||
|
if m.dests.ContainsPrefix(tsaddr.AllIPv4()) ||
|
||||||
m.dests.ContainsPrefix(tsaddr.AllIPv6()) {
|
m.dests.ContainsPrefix(tsaddr.AllIPv6()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if dests contains all prefixes of TheInternet (superset check)
|
// Superset-of-TheInternet check handles merged filter rules
|
||||||
|
// where the internet prefixes are combined with other dests.
|
||||||
theInternet := util.TheInternet()
|
theInternet := util.TheInternet()
|
||||||
for _, prefix := range theInternet.Prefixes() {
|
for _, prefix := range theInternet.Prefixes() {
|
||||||
if !m.dests.ContainsPrefix(prefix) {
|
if !m.dests.ContainsPrefix(prefix) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package matcher
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -428,4 +429,44 @@ func TestDebugString(t *testing.T) {
|
|||||||
assert.Contains(t, s, "Destinations:")
|
assert.Contains(t, s, "Destinations:")
|
||||||
assert.Contains(t, s, "10.0.0.0/8")
|
assert.Contains(t, s, "10.0.0.0/8")
|
||||||
assert.Contains(t, s, "192.168.1.0/24")
|
assert.Contains(t, s, "192.168.1.0/24")
|
||||||
|
|
||||||
|
// Sources appear before Destinations in the output.
|
||||||
|
assert.Less(
|
||||||
|
t,
|
||||||
|
strings.Index(s, "Sources:"),
|
||||||
|
strings.Index(s, "Destinations:"),
|
||||||
|
"Sources section must precede Destinations",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugString_Empty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
m := MatchFromStrings(nil, nil)
|
||||||
|
|
||||||
|
s := m.DebugString()
|
||||||
|
assert.Contains(t, s, "Match:")
|
||||||
|
assert.Contains(t, s, "Sources:")
|
||||||
|
assert.Contains(t, s, "Destinations:")
|
||||||
|
assert.NotContains(t, s, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMatchFromStrings_MalformedFailsOpen asserts that unparseable
|
||||||
|
// entries are silently dropped and do not crash or widen the Match.
|
||||||
|
func TestMatchFromStrings_MalformedFailsOpen(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
m := MatchFromStrings(
|
||||||
|
[]string{"not-a-cidr", "10.0.0.0/8"},
|
||||||
|
[]string{"also-bogus", "192.168.1.0/24"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, m.SrcsContainsIPs(netip.MustParseAddr("10.1.2.3")),
|
||||||
|
"valid src entry must still match")
|
||||||
|
assert.False(t, m.SrcsContainsIPs(netip.MustParseAddr("1.1.1.1")),
|
||||||
|
"malformed src entry must not widen the set")
|
||||||
|
assert.True(t, m.DestsContainsIP(netip.MustParseAddr("192.168.1.10")),
|
||||||
|
"valid dst entry must still match")
|
||||||
|
assert.False(t, m.DestsContainsIP(netip.MustParseAddr("8.8.8.8")),
|
||||||
|
"malformed dst entry must not widen the set")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user