control/controlknobs, all: add plumbed Knobs type, not global variables

Previously two tsnet nodes in the same process couldn't have disjoint
sets of controlknob settings from control as both would overwrite each
other's global variables.

This plumbs a new controlknobs.Knobs type around everywhere and hangs
the knobs sent by control on that instead.

Updates #9351

Change-Id: I75338646d36813ed971b4ffad6f9a8b41ec91560
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2023-09-11 12:03:39 -07:00
committed by Brad Fitzpatrick
parent d050700a3b
commit 4e91cf20a8
19 changed files with 136 additions and 100 deletions

View File

@@ -14,6 +14,7 @@ import (
"sync/atomic"
"testing"
"tailscale.com/control/controlknobs"
"tailscale.com/net/netaddr"
"tailscale.com/types/logger"
)
@@ -249,7 +250,7 @@ func (d *TestIGD) handlePCPQuery(pkt []byte, src netip.AddrPort) {
func newTestClient(t *testing.T, igd *TestIGD) *Client {
var c *Client
c = NewClient(t.Logf, nil, nil, func() {
c = NewClient(t.Logf, nil, nil, new(controlknobs.Knobs), func() {
t.Logf("port map changed")
t.Logf("have mapping: %v", c.HaveMapping())
})

View File

@@ -18,6 +18,7 @@ import (
"time"
"go4.org/mem"
"tailscale.com/control/controlknobs"
"tailscale.com/net/interfaces"
"tailscale.com/net/netaddr"
"tailscale.com/net/neterror"
@@ -66,6 +67,7 @@ const trustServiceStillAvailableDuration = 10 * time.Minute
type Client struct {
logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
controlKnobs *controlknobs.Knobs
ipAndGateway func() (gw, ip netip.Addr, ok bool)
onChange func() // or nil
debug DebugKnobs
@@ -166,15 +168,19 @@ func (m *pmpMapping) Release(ctx context.Context) {
// The debug argument allows configuring the behaviour of the portmapper for
// debugging; if nil, a sensible set of defaults will be used.
//
// The optional onChange argument specifies a func to run in a new
// goroutine whenever the port mapping status has changed. If nil,
// it doesn't make a callback.
func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, onChange func()) *Client {
// The controlKnobs, if non-nil, specifies the control knobs from the control
// plane that might disable portmapping.
//
// The optional onChange argument specifies a func to run in a new goroutine
// whenever the port mapping status has changed. If nil, it doesn't make a
// callback.
func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, controlKnobs *controlknobs.Knobs, onChange func()) *Client {
ret := &Client{
logf: logf,
netMon: netMon,
ipAndGateway: interfaces.LikelyHomeRouterIP,
onChange: onChange,
controlKnobs: controlKnobs,
}
if debug != nil {
ret.debug = *debug

View File

@@ -10,13 +10,15 @@ import (
"strconv"
"testing"
"time"
"tailscale.com/control/controlknobs"
)
func TestCreateOrGetMapping(t *testing.T) {
if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v {
t.Skip("skipping test without HIT_NETWORK=1")
}
c := NewClient(t.Logf, nil, nil, nil)
c := NewClient(t.Logf, nil, nil, new(controlknobs.Knobs), nil)
defer c.Close()
c.SetLocalPort(1234)
for i := 0; i < 2; i++ {
@@ -32,7 +34,7 @@ func TestClientProbe(t *testing.T) {
if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v {
t.Skip("skipping test without HIT_NETWORK=1")
}
c := NewClient(t.Logf, nil, nil, nil)
c := NewClient(t.Logf, nil, nil, new(controlknobs.Knobs), nil)
defer c.Close()
for i := 0; i < 3; i++ {
if i > 0 {
@@ -47,7 +49,7 @@ func TestClientProbeThenMap(t *testing.T) {
if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v {
t.Skip("skipping test without HIT_NETWORK=1")
}
c := NewClient(t.Logf, nil, nil, nil)
c := NewClient(t.Logf, nil, nil, new(controlknobs.Knobs), nil)
defer c.Close()
c.SetLocalPort(1234)
res, err := c.Probe(context.Background())

View File

@@ -24,7 +24,7 @@ import (
"github.com/tailscale/goupnp"
"github.com/tailscale/goupnp/dcps/internetgateway2"
"tailscale.com/control/controlknobs"
"tailscale.com/envknob"
"tailscale.com/net/netns"
"tailscale.com/types/logger"
)
@@ -192,7 +192,7 @@ func addAnyPortMapping(
// The provided ctx is not retained in the returned upnpClient, but
// its associated HTTP client is (if set via goupnp.WithHTTPClient).
func getUPnPClient(ctx context.Context, logf logger.Logf, debug DebugKnobs, gw netip.Addr, meta uPnPDiscoResponse) (client upnpClient, err error) {
if controlknobs.DisableUPnP() || debug.DisableUPnP {
if debug.DisableUPnP {
return nil, nil
}
@@ -270,6 +270,10 @@ func (c *Client) upnpHTTPClientLocked() *http.Client {
return c.uPnPHTTPClient
}
var (
disableUPnpEnv = envknob.RegisterBool("TS_DISABLE_UPNP")
)
// getUPnPPortMapping attempts to create a port-mapping over the UPnP protocol. On success,
// it will return the externally exposed IP and port. Otherwise, it will return a zeroed IP and
// port and an error.
@@ -279,7 +283,7 @@ func (c *Client) getUPnPPortMapping(
internal netip.AddrPort,
prevPort uint16,
) (external netip.AddrPort, ok bool) {
if controlknobs.DisableUPnP() || c.debug.DisableUPnP {
if disableUPnpEnv() || c.debug.DisableUPnP || (c.controlKnobs != nil && c.controlKnobs.DisableUPnP.Load()) {
return netip.AddrPort{}, false
}