net/ipset, wgengine/filter/filtertype: add split-out packages

This moves NewContainsIPFunc from tsaddr to new ipset package.

And wgengine/filter types gets split into wgengine/filter/filtertype,
so netmap (and thus the CLI, etc) doesn't need to bring in ipset,
bart, etc.

Then add a test making sure the CLI deps don't regress.

Updates #1278

Change-Id: Ia246d6d9502bbefbdeacc4aef1bed9c8b24f54d5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2024-06-16 11:34:11 -07:00
committed by Brad Fitzpatrick
parent 36b1b4af2f
commit 86e0f9b912
20 changed files with 388 additions and 347 deletions

View File

@@ -14,9 +14,9 @@ import (
"go4.org/netipx"
"tailscale.com/envknob"
"tailscale.com/net/flowtrack"
"tailscale.com/net/ipset"
"tailscale.com/net/netaddr"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/tstime/rate"
"tailscale.com/types/ipproto"
@@ -24,6 +24,7 @@ import (
"tailscale.com/types/views"
"tailscale.com/util/mak"
"tailscale.com/util/slicesx"
"tailscale.com/wgengine/filter/filtertype"
)
// Filter is a stateful packet filter.
@@ -110,6 +111,13 @@ const (
HexdumpAccepts // print packet hexdump when logging accepts
)
type (
Match = filtertype.Match
NetPortRange = filtertype.NetPortRange
PortRange = filtertype.PortRange
CapMatch = filtertype.CapMatch
)
// NewAllowAllForTest returns a packet filter that accepts
// everything. Use in tests only, as it permits some kinds of spoofing
// attacks to reach the OS network stack.
@@ -192,23 +200,23 @@ func New(matches []Match, localNets, logIPs *netipx.IPSet, shareStateWith *Filte
matches6: matchesFamily(matches, netip.Addr.Is6),
cap4: capMatchesFunc(matches, netip.Addr.Is4),
cap6: capMatchesFunc(matches, netip.Addr.Is6),
local4: tsaddr.FalseContainsIPFunc(),
local6: tsaddr.FalseContainsIPFunc(),
logIPs4: tsaddr.FalseContainsIPFunc(),
logIPs6: tsaddr.FalseContainsIPFunc(),
local4: ipset.FalseContainsIPFunc(),
local6: ipset.FalseContainsIPFunc(),
logIPs4: ipset.FalseContainsIPFunc(),
logIPs6: ipset.FalseContainsIPFunc(),
state: state,
}
if localNets != nil {
p := localNets.Prefixes()
p4, p6 := slicesx.Partition(p, func(p netip.Prefix) bool { return p.Addr().Is4() })
f.local4 = tsaddr.NewContainsIPFunc(views.SliceOf(p4))
f.local6 = tsaddr.NewContainsIPFunc(views.SliceOf(p6))
f.local4 = ipset.NewContainsIPFunc(views.SliceOf(p4))
f.local6 = ipset.NewContainsIPFunc(views.SliceOf(p6))
}
if logIPs != nil {
p := logIPs.Prefixes()
p4, p6 := slicesx.Partition(p, func(p netip.Prefix) bool { return p.Addr().Is4() })
f.logIPs4 = tsaddr.NewContainsIPFunc(views.SliceOf(p4))
f.logIPs6 = tsaddr.NewContainsIPFunc(views.SliceOf(p6))
f.logIPs4 = ipset.NewContainsIPFunc(views.SliceOf(p4))
f.logIPs6 = ipset.NewContainsIPFunc(views.SliceOf(p6))
}
return f
@@ -233,7 +241,7 @@ func matchesFamily(ms matches, keep func(netip.Addr) bool) matches {
}
}
if len(retm.Srcs) > 0 && len(retm.Dsts) > 0 {
retm.SrcsContains = tsaddr.NewContainsIPFunc(views.SliceOf(retm.Srcs))
retm.SrcsContains = ipset.NewContainsIPFunc(views.SliceOf(retm.Srcs))
ret = append(ret, retm)
}
}
@@ -255,7 +263,7 @@ func capMatchesFunc(ms matches, keep func(netip.Addr) bool) matches {
}
}
if len(retm.Srcs) > 0 {
retm.SrcsContains = tsaddr.NewContainsIPFunc(views.SliceOf(retm.Srcs))
retm.SrcsContains = ipset.NewContainsIPFunc(views.SliceOf(retm.Srcs))
ret = append(ret, retm)
}
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"go4.org/netipx"
xmaps "golang.org/x/exp/maps"
"tailscale.com/net/ipset"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
@@ -28,6 +29,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/views"
"tailscale.com/util/must"
"tailscale.com/wgengine/filter/filtertype"
)
// testAllowedProto is an IP protocol number we treat as allowed for
@@ -44,7 +46,7 @@ func m(srcs []netip.Prefix, dsts []NetPortRange, protos ...ipproto.Proto) Match
return Match{
IPProto: protos,
Srcs: srcs,
SrcsContains: tsaddr.NewContainsIPFunc(views.SliceOf(srcs)),
SrcsContains: ipset.NewContainsIPFunc(views.SliceOf(srcs)),
Dsts: dsts,
}
}
@@ -440,7 +442,7 @@ func TestLoggingPrivacy(t *testing.T) {
}
f := newFilter(logf)
f.logIPs4 = tsaddr.NewContainsIPFunc(views.SliceOf([]netip.Prefix{
f.logIPs4 = ipset.NewContainsIPFunc(views.SliceOf([]netip.Prefix{
tsaddr.CGNATRange(),
tsaddr.TailscaleULARange(),
}))
@@ -702,7 +704,7 @@ func nets(nets ...string) (ret []netip.Prefix) {
func ports(s string) PortRange {
if s == "*" {
return allPorts
return filtertype.AllPorts
}
var fs, ls string

View File

@@ -0,0 +1,98 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package filtertype defines the types used by wgengine/filter.
package filtertype
import (
"fmt"
"net/netip"
"strings"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
)
//go:generate go run tailscale.com/cmd/cloner --type=Match,CapMatch
// PortRange is a range of TCP and UDP ports.
type PortRange struct {
First, Last uint16 // inclusive
}
var AllPorts = PortRange{0, 0xffff}
func (pr PortRange) String() string {
if pr.First == 0 && pr.Last == 65535 {
return "*"
} else if pr.First == pr.Last {
return fmt.Sprintf("%d", pr.First)
} else {
return fmt.Sprintf("%d-%d", pr.First, pr.Last)
}
}
// contains returns whether port is in pr.
func (pr PortRange) Contains(port uint16) bool {
return port >= pr.First && port <= pr.Last
}
// NetPortRange combines an IP address prefix and PortRange.
type NetPortRange struct {
Net netip.Prefix
Ports PortRange
}
func (npr NetPortRange) String() string {
return fmt.Sprintf("%v:%v", npr.Net, npr.Ports)
}
// CapMatch is a capability grant match predicate.
type CapMatch struct {
// Dst is the IP prefix that the destination IP address matches against
// to get the capability.
Dst netip.Prefix
// Cap is the capability that's granted if the destination IP addresses
// matches Dst.
Cap tailcfg.PeerCapability
// Values are the raw JSON values of the capability.
// See tailcfg.PeerCapability and tailcfg.PeerCapMap for details.
Values []tailcfg.RawMessage
}
// Match matches packets from any IP address in Srcs to any ip:port in
// Dsts.
type Match struct {
IPProto []ipproto.Proto // required set (no default value at this layer)
Srcs []netip.Prefix
SrcsContains func(netip.Addr) bool `json:"-"` // report whether Addr is in Srcs
Dsts []NetPortRange // optional, if Srcs match
Caps []CapMatch // optional, if Srcs match
}
func (m Match) String() string {
// TODO(bradfitz): use strings.Builder, add String tests
srcs := []string{}
for _, src := range m.Srcs {
srcs = append(srcs, src.String())
}
dsts := []string{}
for _, dst := range m.Dsts {
dsts = append(dsts, dst.String())
}
var ss, ds string
if len(srcs) == 1 {
ss = srcs[0]
} else {
ss = "[" + strings.Join(srcs, ",") + "]"
}
if len(dsts) == 1 {
ds = dsts[0]
} else {
ds = "[" + strings.Join(dsts, ",") + "]"
}
return fmt.Sprintf("%v%v=>%v", m.IPProto, ss, ds)
}

View File

@@ -3,7 +3,7 @@
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
package filter
package filtertype
import (
"net/netip"

View File

@@ -4,101 +4,13 @@
package filter
import (
"fmt"
"net/netip"
"slices"
"strings"
"tailscale.com/net/packet"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
"tailscale.com/wgengine/filter/filtertype"
)
//go:generate go run tailscale.com/cmd/cloner --type=Match,CapMatch
// PortRange is a range of TCP and UDP ports.
type PortRange struct {
First, Last uint16 // inclusive
}
var allPorts = PortRange{0, 0xffff}
func (pr PortRange) String() string {
if pr.First == 0 && pr.Last == 65535 {
return "*"
} else if pr.First == pr.Last {
return fmt.Sprintf("%d", pr.First)
} else {
return fmt.Sprintf("%d-%d", pr.First, pr.Last)
}
}
// contains returns whether port is in pr.
func (pr PortRange) contains(port uint16) bool {
return port >= pr.First && port <= pr.Last
}
// NetPortRange combines an IP address prefix and PortRange.
type NetPortRange struct {
Net netip.Prefix
Ports PortRange
}
func (npr NetPortRange) String() string {
return fmt.Sprintf("%v:%v", npr.Net, npr.Ports)
}
// CapMatch is a capability grant match predicate.
type CapMatch struct {
// Dst is the IP prefix that the destination IP address matches against
// to get the capability.
Dst netip.Prefix
// Cap is the capability that's granted if the destination IP addresses
// matches Dst.
Cap tailcfg.PeerCapability
// Values are the raw JSON values of the capability.
// See tailcfg.PeerCapability and tailcfg.PeerCapMap for details.
Values []tailcfg.RawMessage
}
// Match matches packets from any IP address in Srcs to any ip:port in
// Dsts.
type Match struct {
IPProto []ipproto.Proto // required set (no default value at this layer)
Srcs []netip.Prefix
SrcsContains func(netip.Addr) bool `json:"-"` // report whether Addr is in Srcs
Dsts []NetPortRange // optional, if Srcs match
Caps []CapMatch // optional, if Srcs match
}
func (m Match) String() string {
// TODO(bradfitz): use strings.Builder, add String tests
srcs := []string{}
for _, src := range m.Srcs {
srcs = append(srcs, src.String())
}
dsts := []string{}
for _, dst := range m.Dsts {
dsts = append(dsts, dst.String())
}
var ss, ds string
if len(srcs) == 1 {
ss = srcs[0]
} else {
ss = "[" + strings.Join(srcs, ",") + "]"
}
if len(dsts) == 1 {
ds = dsts[0]
} else {
ds = "[" + strings.Join(dsts, ",") + "]"
}
return fmt.Sprintf("%v%v=>%v", m.IPProto, ss, ds)
}
type matches []Match
type matches []filtertype.Match
func (ms matches) match(q *packet.Parsed) bool {
for _, m := range ms {
@@ -112,7 +24,7 @@ func (ms matches) match(q *packet.Parsed) bool {
if !dst.Net.Contains(q.Dst.Addr()) {
continue
}
if !dst.Ports.contains(q.Dst.Port()) {
if !dst.Ports.Contains(q.Dst.Port()) {
continue
}
return true
@@ -147,7 +59,7 @@ func (ms matches) matchProtoAndIPsOnlyIfAllPorts(q *packet.Parsed) bool {
continue
}
for _, dst := range m.Dsts {
if dst.Ports != allPorts {
if dst.Ports != filtertype.AllPorts {
continue
}
if dst.Net.Contains(q.Dst.Addr()) {

View File

@@ -9,8 +9,8 @@ import (
"strings"
"go4.org/netipx"
"tailscale.com/net/ipset"
"tailscale.com/net/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
"tailscale.com/types/views"
@@ -63,7 +63,7 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
}
m.Srcs = append(m.Srcs, nets...)
}
m.SrcsContains = tsaddr.NewContainsIPFunc(views.SliceOf(m.Srcs))
m.SrcsContains = ipset.NewContainsIPFunc(views.SliceOf(m.Srcs))
for _, d := range r.DstPorts {
nets, err := parseIPSet(d.IP, d.Bits)