mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-01 09:32:08 +00:00
tailcfg,ipn/ipnlocal,wgengine: add values to PeerCapabilities
Define PeerCapabilty and PeerCapMap as the new way of sending down inter-peer capability information. Previously, this was unstructured and you could only send down strings which got too limiting for certain usecases. Instead add the ability to send down raw JSON messages that are opaque to Tailscale but provide the applications to define them however they wish. Also update accessors to use the new values. Updates #4217 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
@@ -11,13 +11,16 @@ import (
|
||||
"time"
|
||||
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/flowtrack"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstime/rate"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
// Filter is a stateful packet filter.
|
||||
@@ -322,10 +325,9 @@ func (f *Filter) CheckTCP(srcIP, dstIP netip.Addr, dstPort uint16) Response {
|
||||
return f.RunIn(pkt, 0)
|
||||
}
|
||||
|
||||
// AppendCaps appends to base the capabilities that srcIP has talking
|
||||
// CapsWithValues appends to base the capabilities that srcIP has talking
|
||||
// to dstIP.
|
||||
func (f *Filter) AppendCaps(base []string, srcIP, dstIP netip.Addr) []string {
|
||||
ret := base
|
||||
func (f *Filter) CapsWithValues(srcIP, dstIP netip.Addr) tailcfg.PeerCapMap {
|
||||
var mm matches
|
||||
switch {
|
||||
case srcIP.Is4():
|
||||
@@ -333,17 +335,23 @@ func (f *Filter) AppendCaps(base []string, srcIP, dstIP netip.Addr) []string {
|
||||
case srcIP.Is6():
|
||||
mm = f.cap6
|
||||
}
|
||||
var out tailcfg.PeerCapMap
|
||||
for _, m := range mm {
|
||||
if !ipInList(srcIP, m.Srcs) {
|
||||
continue
|
||||
}
|
||||
for _, cm := range m.Caps {
|
||||
if cm.Cap != "" && cm.Dst.Contains(dstIP) {
|
||||
ret = append(ret, cm.Cap)
|
||||
prev, ok := out[cm.Cap]
|
||||
if !ok {
|
||||
mak.Set(&out, cm.Cap, slices.Clone(cm.Values))
|
||||
continue
|
||||
}
|
||||
out[cm.Cap] = append(prev, cm.Values...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
return out
|
||||
}
|
||||
|
||||
// ShieldsUp reports whether this is a "shields up" (block everything
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
@@ -22,7 +24,10 @@ func (src *Match) Clone() *Match {
|
||||
dst.IPProto = append(src.IPProto[:0:0], src.IPProto...)
|
||||
dst.Srcs = append(src.Srcs[:0:0], src.Srcs...)
|
||||
dst.Dsts = append(src.Dsts[:0:0], src.Dsts...)
|
||||
dst.Caps = append(src.Caps[:0:0], src.Caps...)
|
||||
dst.Caps = make([]CapMatch, len(src.Caps))
|
||||
for i := range dst.Caps {
|
||||
dst.Caps[i] = *src.Caps[i].Clone()
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
@@ -33,3 +38,25 @@ var _MatchCloneNeedsRegeneration = Match(struct {
|
||||
Dsts []NetPortRange
|
||||
Caps []CapMatch
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of CapMatch.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *CapMatch) Clone() *CapMatch {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(CapMatch)
|
||||
*dst = *src
|
||||
dst.Values = make([]json.RawMessage, len(src.Values))
|
||||
for i := range dst.Values {
|
||||
dst.Values[i] = append(src.Values[i][:0:0], src.Values[i]...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _CapMatchCloneNeedsRegeneration = CapMatch(struct {
|
||||
Dst netip.Prefix
|
||||
Cap tailcfg.PeerCapability
|
||||
Values []json.RawMessage
|
||||
}{})
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -880,7 +881,7 @@ func TestCaps(t *testing.T) {
|
||||
Dsts: []netip.Prefix{
|
||||
netip.MustParsePrefix("0.0.0.0/0"),
|
||||
},
|
||||
Caps: []string{"is_ipv4"},
|
||||
Caps: []tailcfg.PeerCapability{"is_ipv4"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
@@ -889,7 +890,7 @@ func TestCaps(t *testing.T) {
|
||||
Dsts: []netip.Prefix{
|
||||
netip.MustParsePrefix("::/0"),
|
||||
},
|
||||
Caps: []string{"is_ipv6"},
|
||||
Caps: []tailcfg.PeerCapability{"is_ipv6"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
@@ -898,7 +899,7 @@ func TestCaps(t *testing.T) {
|
||||
Dsts: []netip.Prefix{
|
||||
netip.MustParsePrefix("100.200.0.0/16"),
|
||||
},
|
||||
Caps: []string{"some_super_admin"},
|
||||
Caps: []tailcfg.PeerCapability{"some_super_admin"},
|
||||
}},
|
||||
},
|
||||
})
|
||||
@@ -909,43 +910,45 @@ func TestCaps(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
src, dst string // IP
|
||||
want []string
|
||||
want []tailcfg.PeerCapability
|
||||
}{
|
||||
{
|
||||
name: "v4",
|
||||
src: "1.2.3.4",
|
||||
dst: "2.4.5.5",
|
||||
want: []string{"is_ipv4"},
|
||||
want: []tailcfg.PeerCapability{"is_ipv4"},
|
||||
},
|
||||
{
|
||||
name: "v6",
|
||||
src: "1::1",
|
||||
dst: "2::2",
|
||||
want: []string{"is_ipv6"},
|
||||
want: []tailcfg.PeerCapability{"is_ipv6"},
|
||||
},
|
||||
{
|
||||
name: "admin",
|
||||
src: "100.199.1.2",
|
||||
dst: "100.200.3.4",
|
||||
want: []string{"is_ipv4", "some_super_admin"},
|
||||
want: []tailcfg.PeerCapability{"is_ipv4", "some_super_admin"},
|
||||
},
|
||||
{
|
||||
name: "not_admin_bad_src",
|
||||
src: "100.198.1.2", // 198, not 199
|
||||
dst: "100.200.3.4",
|
||||
want: []string{"is_ipv4"},
|
||||
want: []tailcfg.PeerCapability{"is_ipv4"},
|
||||
},
|
||||
{
|
||||
name: "not_admin_bad_dst",
|
||||
src: "100.199.1.2",
|
||||
dst: "100.201.3.4", // 201, not 200
|
||||
want: []string{"is_ipv4"},
|
||||
want: []tailcfg.PeerCapability{"is_ipv4"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := filt.AppendCaps(nil, netip.MustParseAddr(tt.src), netip.MustParseAddr(tt.dst))
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
got := maps.Keys(filt.CapsWithValues(netip.MustParseAddr(tt.src), netip.MustParseAddr(tt.dst)))
|
||||
slices.Sort(got)
|
||||
slices.Sort(tt.want)
|
||||
if !slices.Equal(got, tt.want) {
|
||||
t.Errorf("got %q; want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/cloner --type=Match
|
||||
//go:generate go run tailscale.com/cmd/cloner --type=Match,CapMatch
|
||||
|
||||
// PortRange is a range of TCP and UDP ports.
|
||||
type PortRange struct {
|
||||
@@ -54,7 +56,11 @@ type CapMatch struct {
|
||||
|
||||
// Cap is the capability that's granted if the destination IP addresses
|
||||
// matches Dst.
|
||||
Cap string
|
||||
Cap tailcfg.PeerCapability
|
||||
|
||||
// Values are the raw JSON values of the capability.
|
||||
// See tailcfg.PeerCapability and tailcfg.PeerCapMap for details.
|
||||
Values []json.RawMessage
|
||||
}
|
||||
|
||||
// Match matches packets from any IP address in Srcs to any ip:port in
|
||||
|
||||
@@ -86,6 +86,13 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
|
||||
Cap: cap,
|
||||
})
|
||||
}
|
||||
for cap, val := range cm.CapMap {
|
||||
m.Caps = append(m.Caps, CapMatch{
|
||||
Dst: dstNet,
|
||||
Cap: tailcfg.PeerCapability(cap),
|
||||
Values: val,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user