mirror of https://github.com/tailscale/tailscale/
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>pull/12500/head
parent
36b1b4af2f
commit
86e0f9b912
@ -0,0 +1,80 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package ipset provides code for creating efficient IP-in-set lookup functions
|
||||
// with different implementations depending on the set.
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
// FalseContainsIPFunc is shorthand for NewContainsIPFunc(views.Slice[netip.Prefix]{}).
|
||||
func FalseContainsIPFunc() func(ip netip.Addr) bool {
|
||||
return func(ip netip.Addr) bool { return false }
|
||||
}
|
||||
|
||||
// pathForTest is a test hook for NewContainsIPFunc, to test that it took the
|
||||
// right construction path.
|
||||
var pathForTest = func(string) {}
|
||||
|
||||
// NewContainsIPFunc returns a func that reports whether ip is in addrs.
|
||||
//
|
||||
// The returned func is optimized for the length of contents of addrs.
|
||||
func NewContainsIPFunc(addrs views.Slice[netip.Prefix]) func(ip netip.Addr) bool {
|
||||
// Specialize the three common cases: no address, just IPv4
|
||||
// (or just IPv6), and both IPv4 and IPv6.
|
||||
if addrs.Len() == 0 {
|
||||
pathForTest("empty")
|
||||
return func(netip.Addr) bool { return false }
|
||||
}
|
||||
// If any addr is a prefix with more than a single IP, then do either a
|
||||
// linear scan or a bart table, depending on the number of addrs.
|
||||
if addrs.ContainsFunc(func(p netip.Prefix) bool { return !p.IsSingleIP() }) {
|
||||
if addrs.Len() > 6 {
|
||||
pathForTest("bart")
|
||||
// Built a bart table.
|
||||
t := &bart.Table[struct{}]{}
|
||||
for i := range addrs.Len() {
|
||||
t.Insert(addrs.At(i), struct{}{})
|
||||
}
|
||||
return func(ip netip.Addr) bool {
|
||||
_, ok := t.Get(ip)
|
||||
return ok
|
||||
}
|
||||
} else {
|
||||
pathForTest("linear-contains")
|
||||
// Small enough to do a linear search.
|
||||
acopy := addrs.AsSlice()
|
||||
return func(ip netip.Addr) bool {
|
||||
for _, a := range acopy {
|
||||
if a.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fast paths for 1 and 2 IPs:
|
||||
if addrs.Len() == 1 {
|
||||
pathForTest("one-ip")
|
||||
a := addrs.At(0)
|
||||
return func(ip netip.Addr) bool { return ip == a.Addr() }
|
||||
}
|
||||
if addrs.Len() == 2 {
|
||||
pathForTest("two-ip")
|
||||
a, b := addrs.At(0), addrs.At(1)
|
||||
return func(ip netip.Addr) bool { return ip == a.Addr() || ip == b.Addr() }
|
||||
}
|
||||
// General case:
|
||||
pathForTest("ip-map")
|
||||
m := map[netip.Addr]bool{}
|
||||
for i := range addrs.Len() {
|
||||
m[addrs.At(i).Addr()] = true
|
||||
}
|
||||
return func(ip netip.Addr) bool { return m[ip] }
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
func pp(ss ...string) (ret []netip.Prefix) {
|
||||
for _, s := range ss {
|
||||
ret = append(ret, netip.MustParsePrefix(s))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func aa(ss ...string) (ret []netip.Addr) {
|
||||
for _, s := range ss {
|
||||
ret = append(ret, netip.MustParseAddr(s))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var newContainsIPFuncTests = []struct {
|
||||
name string
|
||||
pfx []netip.Prefix
|
||||
want string
|
||||
wantIn []netip.Addr
|
||||
wantOut []netip.Addr
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
pfx: pp(),
|
||||
want: "empty",
|
||||
wantOut: aa("8.8.8.8"),
|
||||
},
|
||||
{
|
||||
name: "cidr-list-1",
|
||||
pfx: pp("10.0.0.0/8"),
|
||||
want: "linear-contains",
|
||||
wantIn: aa("10.0.0.1", "10.2.3.4"),
|
||||
wantOut: aa("8.8.8.8"),
|
||||
},
|
||||
{
|
||||
name: "cidr-list-2",
|
||||
pfx: pp("1.0.0.0/8", "3.0.0.0/8"),
|
||||
want: "linear-contains",
|
||||
wantIn: aa("1.0.0.1", "3.0.0.1"),
|
||||
wantOut: aa("2.0.0.1"),
|
||||
},
|
||||
{
|
||||
name: "cidr-list-3",
|
||||
pfx: pp("1.0.0.0/8", "3.0.0.0/8", "5.0.0.0/8"),
|
||||
want: "linear-contains",
|
||||
wantIn: aa("1.0.0.1", "5.0.0.1"),
|
||||
wantOut: aa("2.0.0.1"),
|
||||
},
|
||||
{
|
||||
name: "cidr-list-4",
|
||||
pfx: pp("1.0.0.0/8", "3.0.0.0/8", "5.0.0.0/8", "7.0.0.0/8"),
|
||||
want: "linear-contains",
|
||||
wantIn: aa("1.0.0.1", "7.0.0.1"),
|
||||
wantOut: aa("2.0.0.1"),
|
||||
},
|
||||
{
|
||||
name: "cidr-list-5",
|
||||
pfx: pp("1.0.0.0/8", "3.0.0.0/8", "5.0.0.0/8", "7.0.0.0/8", "9.0.0.0/8"),
|
||||
want: "linear-contains",
|
||||
wantIn: aa("1.0.0.1", "9.0.0.1"),
|
||||
wantOut: aa("2.0.0.1"),
|
||||
},
|
||||
{
|
||||
name: "cidr-list-10",
|
||||
pfx: pp("1.0.0.0/8", "3.0.0.0/8", "5.0.0.0/8", "7.0.0.0/8", "9.0.0.0/8",
|
||||
"11.0.0.0/8", "13.0.0.0/8", "15.0.0.0/8", "17.0.0.0/8", "19.0.0.0/8"),
|
||||
want: "bart", // big enough that bart is faster than linear-contains
|
||||
wantIn: aa("1.0.0.1", "19.0.0.1"),
|
||||
wantOut: aa("2.0.0.1"),
|
||||
},
|
||||
{
|
||||
name: "one-ip",
|
||||
pfx: pp("10.1.0.0/32"),
|
||||
want: "one-ip",
|
||||
wantIn: aa("10.1.0.0"),
|
||||
wantOut: aa("10.0.0.9"),
|
||||
},
|
||||
{
|
||||
name: "two-ip",
|
||||
pfx: pp("10.1.0.0/32", "10.2.0.0/32"),
|
||||
want: "two-ip",
|
||||
wantIn: aa("10.1.0.0", "10.2.0.0"),
|
||||
wantOut: aa("8.8.8.8"),
|
||||
},
|
||||
{
|
||||
name: "three-ip",
|
||||
pfx: pp("10.1.0.0/32", "10.2.0.0/32", "10.3.0.0/32"),
|
||||
want: "ip-map",
|
||||
wantIn: aa("10.1.0.0", "10.2.0.0"),
|
||||
wantOut: aa("8.8.8.8"),
|
||||
},
|
||||
}
|
||||
|
||||
func BenchmarkNewContainsIPFunc(b *testing.B) {
|
||||
for _, tt := range newContainsIPFuncTests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
f := NewContainsIPFunc(views.SliceOf(tt.pfx))
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, ip := range tt.wantIn {
|
||||
if !f(ip) {
|
||||
b.Fatal("unexpected false")
|
||||
}
|
||||
}
|
||||
for _, ip := range tt.wantOut {
|
||||
if f(ip) {
|
||||
b.Fatal("unexpected true")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewContainsIPFunc(t *testing.T) {
|
||||
for _, tt := range newContainsIPFuncTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got string
|
||||
tstest.Replace(t, &pathForTest, func(path string) { got = path })
|
||||
|
||||
f := NewContainsIPFunc(views.SliceOf(tt.pfx))
|
||||
if got != tt.want {
|
||||
t.Errorf("func type = %q; want %q", got, tt.want)
|
||||
}
|
||||
for _, ip := range tt.wantIn {
|
||||
if !f(ip) {
|
||||
t.Errorf("match(%v) = false; want true", ip)
|
||||
}
|
||||
}
|
||||
for _, ip := range tt.wantOut {
|
||||
if f(ip) {
|
||||
t.Errorf("match(%v) = true; want false", ip)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue