ipn/ipnlocal: connect serve config to c2n endpoint

This commit updates the VIPService c2n endpoint on client to response with actual VIPService configuration stored
in the serve config.

Fixes tailscale/corp#24510
Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
This commit is contained in:
KevinLiang10
2025-01-06 11:27:11 -05:00
parent 60daa2adb8
commit 009da8a364
3 changed files with 154 additions and 34 deletions

View File

@@ -11,6 +11,7 @@ import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -5017,13 +5018,7 @@ 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 = ""
}
hi.ServicesHash = b.vipServiceHashLocked(prefs)
// The Hostinfo.WantIngress field tells control whether this node wants to
// be wired up for ingress connections. If harmless if it's accidentally
@@ -7659,28 +7654,38 @@ func maybeUsernameOf(actor ipnauth.Actor) string {
func (b *LocalBackend) VIPServices() []*tailcfg.VIPService {
b.mu.Lock()
defer b.mu.Unlock()
return vipServicesFromPrefs(b.pm.CurrentPrefs())
return b.vipServicesFromPrefsLocked(b.pm.CurrentPrefs())
}
func vipServicesFromPrefs(prefs ipn.PrefsView) []*tailcfg.VIPService {
func (b *LocalBackend) vipServiceHashLocked(prefs ipn.PrefsView) string {
services := b.vipServicesFromPrefsLocked(prefs)
if len(services) == 0 {
return ""
}
buf, err := json.Marshal(services)
if err != nil {
b.logf("vipServiceHashLocked: %v", err)
return ""
}
hash := sha256.Sum256(buf)
return hex.EncodeToString(hash[:])
}
func (b *LocalBackend) vipServicesFromPrefsLocked(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, ",")
if !b.serveConfig.Valid() {
return nil
}
for _, s := range allPortsServices {
mak.Set(&services, s, &tailcfg.VIPService{
Name: s,
Ports: []tailcfg.ProtoPortRange{{Ports: tailcfg.PortRangeAny}},
for svc, config := range b.serveConfig.Services().All() {
mak.Set(&services, svc, &tailcfg.VIPService{
Name: svc,
Ports: config.ServicePortRange(),
})
}
for _, s := range prefs.AdvertiseServices().AsSlice() {
for _, s := range prefs.AdvertiseServices().All() {
if services == nil || services[s] == nil {
mak.Set(&services, s, &tailcfg.VIPService{
Name: s,

View File

@@ -30,7 +30,6 @@ import (
"tailscale.com/control/controlclient"
"tailscale.com/drive"
"tailscale.com/drive/driveimpl"
"tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
@@ -4509,15 +4508,15 @@ func TestConfigFileReload(t *testing.T) {
func TestGetVIPServices(t *testing.T) {
tests := []struct {
name string
advertised []string
mapped []string
want []*tailcfg.VIPService
name string
advertised []string
serveConfig *ipn.ServeConfig
want []*tailcfg.VIPService
}{
{
"advertised-only",
[]string{"svc:abc", "svc:def"},
[]string{},
&ipn.ServeConfig{},
[]*tailcfg.VIPService{
{
Name: "svc:abc",
@@ -4530,9 +4529,13 @@ func TestGetVIPServices(t *testing.T) {
},
},
{
"mapped-only",
"served-only",
[]string{},
[]string{"svc:abc"},
&ipn.ServeConfig{
Services: map[string]*ipn.ServiceConfig{
"svc:abc": {Tun: true},
},
},
[]*tailcfg.VIPService{
{
Name: "svc:abc",
@@ -4541,9 +4544,13 @@ func TestGetVIPServices(t *testing.T) {
},
},
{
"mapped-and-advertised",
[]string{"svc:abc"},
"served-and-advertised",
[]string{"svc:abc"},
&ipn.ServeConfig{
Services: map[string]*ipn.ServiceConfig{
"svc:abc": {Tun: true},
},
},
[]*tailcfg.VIPService{
{
Name: "svc:abc",
@@ -4553,9 +4560,13 @@ func TestGetVIPServices(t *testing.T) {
},
},
{
"mapped-and-advertised-separately",
"served-and-advertised-different-service",
[]string{"svc:def"},
[]string{"svc:abc"},
&ipn.ServeConfig{
Services: map[string]*ipn.ServiceConfig{
"svc:abc": {Tun: true},
},
},
[]*tailcfg.VIPService{
{
Name: "svc:abc",
@@ -4567,14 +4578,78 @@ func TestGetVIPServices(t *testing.T) {
},
},
},
{
"served-with-port-ranges-one-range-single",
[]string{},
&ipn.ServeConfig{
Services: map[string]*ipn.ServiceConfig{
"svc:abc": {TCP: map[uint16]*ipn.TCPPortHandler{
80: {HTTPS: true},
}},
},
},
[]*tailcfg.VIPService{
{
Name: "svc:abc",
Ports: []tailcfg.ProtoPortRange{{Proto: 6, Ports: tailcfg.PortRange{First: 80, Last: 80}}},
},
},
},
{
"served-with-port-ranges-one-range-multiple",
[]string{},
&ipn.ServeConfig{
Services: map[string]*ipn.ServiceConfig{
"svc:abc": {TCP: map[uint16]*ipn.TCPPortHandler{
80: {HTTPS: true},
81: {HTTPS: true},
82: {HTTPS: true},
}},
},
},
[]*tailcfg.VIPService{
{
Name: "svc:abc",
Ports: []tailcfg.ProtoPortRange{{Proto: 6, Ports: tailcfg.PortRange{First: 80, Last: 82}}},
},
},
},
{
"served-with-port-ranges-multiple-ranges",
[]string{},
&ipn.ServeConfig{
Services: map[string]*ipn.ServiceConfig{
"svc:abc": {TCP: map[uint16]*ipn.TCPPortHandler{
80: {HTTPS: true},
81: {HTTPS: true},
82: {HTTPS: true},
1212: {HTTPS: true},
1213: {HTTPS: true},
1214: {HTTPS: true},
}},
},
},
[]*tailcfg.VIPService{
{
Name: "svc:abc",
Ports: []tailcfg.ProtoPortRange{
{Proto: 6, Ports: tailcfg.PortRange{First: 80, Last: 82}},
{Proto: 6, Ports: tailcfg.PortRange{First: 1212, Last: 1214}},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
envknob.Setenv("TS_DEBUG_ALLPORTS_SERVICES", strings.Join(tt.mapped, ","))
lb := newLocalBackendWithTestControl(t, false, func(tb testing.TB, opts controlclient.Options) controlclient.Client {
return newClient(tb, opts)
})
lb.serveConfig = tt.serveConfig.View()
prefs := &ipn.Prefs{
AdvertiseServices: tt.advertised,
}
got := vipServicesFromPrefs(prefs.View())
got := lb.vipServicesFromPrefsLocked(prefs.View())
slices.SortFunc(got, func(a, b *tailcfg.VIPService) int {
return strings.Compare(a.Name, b.Name)
})