derp, wgengine: let clients advertise an opaque app name to DERP servers
Add an AppName field to the DERP ClientInfo so DERP servers can attribute connections to the application making them, primarily for best effort stats purposes. The value is plumbed per engine instance rather than via a process global, so a process hosting multiple stacks can attribute each one's DERP connections separately: wgengine.Config.DERPAppName flows through magicsock.Options and derphttp.Client into the naclbox-sealed ClientInfo JSON. Old servers ignore the unknown field. There are no callers in the tree yet setting the name. Updates tailscale/corp#24454 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: Ia7d3e9c2b6f8140e5a9d7c3b2e6f1a8d4c0b5e9f
This commit is contained in:
committed by
Brad Fitzpatrick
parent
37e175a032
commit
246c82a658
@@ -4,9 +4,13 @@
|
||||
package wgengine
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
@@ -19,12 +23,15 @@ import (
|
||||
"go4.org/mem"
|
||||
"tailscale.com/cmd/testwrapper/flakytest"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/derp/derpserver"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/dns/resolver"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/stun/stuntest"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/key"
|
||||
@@ -553,3 +560,86 @@ func TestCloseWaitsForLinkChange(t *testing.T) {
|
||||
t.Fatal("Close returned with link change work still in flight")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDERPAppNamePlumbing tests that Config.DERPAppName makes it all
|
||||
// the way from the engine config to the ClientInfo received by an
|
||||
// in-process DERP server.
|
||||
func TestDERPAppNamePlumbing(t *testing.T) {
|
||||
const appName = "app-name-plumbing-test"
|
||||
|
||||
priv := key.NewNode()
|
||||
infoCh := make(chan derp.ClientInfo, 1)
|
||||
|
||||
derpSrv := derpserver.New(key.NewNode(), t.Logf)
|
||||
derpSrv.ForTest().SetOnClientInfo(func(k key.NodePublic, info derp.ClientInfo) {
|
||||
if k != priv.Public() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case infoCh <- info:
|
||||
default:
|
||||
}
|
||||
})
|
||||
httpsrv := httptest.NewUnstartedServer(derpserver.Handler(derpSrv))
|
||||
httpsrv.Config.ErrorLog = logger.StdLogger(t.Logf)
|
||||
httpsrv.Config.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
||||
httpsrv.StartTLS()
|
||||
t.Cleanup(func() {
|
||||
httpsrv.CloseClientConnections()
|
||||
httpsrv.Close()
|
||||
derpSrv.Close()
|
||||
})
|
||||
|
||||
stunAddr, stunCleanup := stuntest.Serve(t)
|
||||
t.Cleanup(stunCleanup)
|
||||
|
||||
derpMap := &tailcfg.DERPMap{
|
||||
Regions: map[int]*tailcfg.DERPRegion{
|
||||
1: {
|
||||
RegionID: 1,
|
||||
RegionCode: "test",
|
||||
Nodes: []*tailcfg.DERPNode{{
|
||||
Name: "t1",
|
||||
RegionID: 1,
|
||||
HostName: "test-node.unused",
|
||||
IPv4: "127.0.0.1",
|
||||
IPv6: "none",
|
||||
STUNPort: stunAddr.Port,
|
||||
DERPPort: httpsrv.Listener.Addr().(*net.TCPAddr).Port,
|
||||
InsecureForTests: true,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bus := eventbustest.NewBus(t)
|
||||
noopDNS, err := dns.NewNoopManager()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
e, err := NewUserspaceEngine(t.Logf, Config{
|
||||
HealthTracker: health.NewTracker(bus),
|
||||
Metrics: new(usermetric.Registry),
|
||||
EventBus: bus,
|
||||
DNS: noopDNS,
|
||||
DERPAppName: appName,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(e.Close)
|
||||
|
||||
if err := e.Reconfig(&wgcfg.Config{PrivateKey: priv}, &router.Config{}, &dns.Config{}); err != nil {
|
||||
t.Fatalf("Reconfig: %v", err)
|
||||
}
|
||||
e.(*userspaceEngine).magicConn.SetDERPMap(derpMap)
|
||||
|
||||
select {
|
||||
case info := <-infoCh:
|
||||
if info.AppName != appName {
|
||||
t.Fatalf("ClientInfo.AppName = %q; want %q", info.AppName, appName)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("timeout waiting for engine to connect to the test DERP server")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user