mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-12 05:37:32 +00:00
ipn,tailcfg: add VIPService struct and c2n to fetch them from client (#14046)
* ipn,tailcfg: add VIPService struct and c2n to fetch them from client Updates tailscale/corp#22743, tailscale/corp#22955 Signed-off-by: Naman Sood <mail@nsood.in> * more review fixes Signed-off-by: Naman Sood <mail@nsood.in> * don't mention PeerCapabilityServicesDestination since it's currently unused Signed-off-by: Naman Sood <mail@nsood.in> --------- Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
@@ -77,6 +77,9 @@ var c2nHandlers = map[methodAndPath]c2nHandler{
|
||||
|
||||
// Linux netfilter.
|
||||
req("POST /netfilter-kind"): handleC2NSetNetfilterKind,
|
||||
|
||||
// VIP services.
|
||||
req("GET /vip-services"): handleC2NVIPServicesGet,
|
||||
}
|
||||
|
||||
type c2nHandler func(*LocalBackend, http.ResponseWriter, *http.Request)
|
||||
@@ -269,6 +272,12 @@ func handleC2NSetNetfilterKind(b *LocalBackend, w http.ResponseWriter, r *http.R
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func handleC2NVIPServicesGet(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
|
||||
b.logf("c2n: GET /vip-services received")
|
||||
|
||||
json.NewEncoder(w).Encode(b.VIPServices())
|
||||
}
|
||||
|
||||
func handleC2NUpdateGet(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
|
||||
b.logf("c2n: GET /update received")
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -4888,6 +4889,14 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip
|
||||
}
|
||||
hi.SSH_HostKeys = sshHostKeys
|
||||
|
||||
services := vipServicesFromPrefs(prefs)
|
||||
if len(services) > 0 {
|
||||
buf, _ := json.Marshal(services)
|
||||
hi.ServicesHash = fmt.Sprintf("%02x", sha256.Sum256(buf))
|
||||
} else {
|
||||
hi.ServicesHash = ""
|
||||
}
|
||||
|
||||
// The Hostinfo.WantIngress field tells control whether this node wants to
|
||||
// be wired up for ingress connections. If harmless if it's accidentally
|
||||
// true; the actual policy is controlled in tailscaled by ServeConfig. But
|
||||
@@ -7485,3 +7494,42 @@ func maybeUsernameOf(actor ipnauth.Actor) string {
|
||||
}
|
||||
return username
|
||||
}
|
||||
|
||||
// VIPServices returns the list of tailnet services that this node
|
||||
// is serving as a destination for.
|
||||
// The returned memory is owned by the caller.
|
||||
func (b *LocalBackend) VIPServices() []*tailcfg.VIPService {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return vipServicesFromPrefs(b.pm.CurrentPrefs())
|
||||
}
|
||||
|
||||
func vipServicesFromPrefs(prefs ipn.PrefsView) []*tailcfg.VIPService {
|
||||
// keyed by service name
|
||||
var services map[string]*tailcfg.VIPService
|
||||
|
||||
// TODO(naman): this envknob will be replaced with service-specific port
|
||||
// information once we start storing that.
|
||||
var allPortsServices []string
|
||||
if env := envknob.String("TS_DEBUG_ALLPORTS_SERVICES"); env != "" {
|
||||
allPortsServices = strings.Split(env, ",")
|
||||
}
|
||||
|
||||
for _, s := range allPortsServices {
|
||||
mak.Set(&services, s, &tailcfg.VIPService{
|
||||
Name: s,
|
||||
Ports: []tailcfg.ProtoPortRange{{Ports: tailcfg.PortRangeAny}},
|
||||
})
|
||||
}
|
||||
|
||||
for _, s := range prefs.AdvertiseServices().AsSlice() {
|
||||
if services == nil || services[s] == nil {
|
||||
mak.Set(&services, s, &tailcfg.VIPService{
|
||||
Name: s,
|
||||
})
|
||||
}
|
||||
services[s].Active = true
|
||||
}
|
||||
|
||||
return slices.Collect(maps.Values(services))
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import (
|
||||
"tailscale.com/control/controlclient"
|
||||
"tailscale.com/drive"
|
||||
"tailscale.com/drive/driveimpl"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/ipn"
|
||||
@@ -4464,3 +4465,90 @@ func TestConfigFileReload(t *testing.T) {
|
||||
t.Fatalf("got %q; want %q", hn, "bar")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVIPServices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
advertised []string
|
||||
mapped []string
|
||||
want []*tailcfg.VIPService
|
||||
}{
|
||||
{
|
||||
"advertised-only",
|
||||
[]string{"svc:abc", "svc:def"},
|
||||
[]string{},
|
||||
[]*tailcfg.VIPService{
|
||||
{
|
||||
Name: "svc:abc",
|
||||
Active: true,
|
||||
},
|
||||
{
|
||||
Name: "svc:def",
|
||||
Active: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"mapped-only",
|
||||
[]string{},
|
||||
[]string{"svc:abc"},
|
||||
[]*tailcfg.VIPService{
|
||||
{
|
||||
Name: "svc:abc",
|
||||
Ports: []tailcfg.ProtoPortRange{{Ports: tailcfg.PortRangeAny}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"mapped-and-advertised",
|
||||
[]string{"svc:abc"},
|
||||
[]string{"svc:abc"},
|
||||
[]*tailcfg.VIPService{
|
||||
{
|
||||
Name: "svc:abc",
|
||||
Active: true,
|
||||
Ports: []tailcfg.ProtoPortRange{{Ports: tailcfg.PortRangeAny}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"mapped-and-advertised-separately",
|
||||
[]string{"svc:def"},
|
||||
[]string{"svc:abc"},
|
||||
[]*tailcfg.VIPService{
|
||||
{
|
||||
Name: "svc:abc",
|
||||
Ports: []tailcfg.ProtoPortRange{{Ports: tailcfg.PortRangeAny}},
|
||||
},
|
||||
{
|
||||
Name: "svc:def",
|
||||
Active: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
envknob.Setenv("TS_DEBUG_ALLPORTS_SERVICES", strings.Join(tt.mapped, ","))
|
||||
prefs := &ipn.Prefs{
|
||||
AdvertiseServices: tt.advertised,
|
||||
}
|
||||
got := vipServicesFromPrefs(prefs.View())
|
||||
slices.SortFunc(got, func(a, b *tailcfg.VIPService) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
if !reflect.DeepEqual(tt.want, got) {
|
||||
t.Logf("want:")
|
||||
for _, s := range tt.want {
|
||||
t.Logf("%+v", s)
|
||||
}
|
||||
t.Logf("got:")
|
||||
for _, s := range got {
|
||||
t.Logf("%+v", s)
|
||||
}
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user