summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cmd/k8s-operator/depaware.txt1
-rw-r--r--cmd/tailscaled/depaware.txt1
-rw-r--r--ipn/ipnlocal/local.go51
-rw-r--r--ipn/ipnlocal/peerapi.go53
-rw-r--r--ipn/ipnlocal/relay_default.go39
-rw-r--r--ipn/ipnlocal/relay_ios.go10
-rw-r--r--net/udprelay/server_default.go (renamed from net/udprelay/server.go)2
-rw-r--r--net/udprelay/server_default_test.go (renamed from net/udprelay/server_test.go)2
-rw-r--r--net/udprelay/server_ios.go8
9 files changed, 167 insertions, 0 deletions
diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt
index 7fd4c4b21..87dfe9815 100644
--- a/cmd/k8s-operator/depaware.txt
+++ b/cmd/k8s-operator/depaware.txt
@@ -883,6 +883,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
tailscale.com/net/tstun from tailscale.com/tsd+
+ tailscale.com/net/udprelay from tailscale.com/ipn/ipnlocal
tailscale.com/omit from tailscale.com/ipn/conffile
tailscale.com/paths from tailscale.com/client/local+
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt
index 394056295..64c8a4892 100644
--- a/cmd/tailscaled/depaware.txt
+++ b/cmd/tailscaled/depaware.txt
@@ -333,6 +333,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/tsdial from tailscale.com/cmd/tailscaled+
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
+ tailscale.com/net/udprelay from tailscale.com/ipn/ipnlocal
tailscale.com/omit from tailscale.com/ipn/conffile
tailscale.com/paths from tailscale.com/client/local+
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 7d69b884d..59ff81bf8 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -76,6 +76,7 @@ import (
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial"
+ "tailscale.com/net/udprelay"
"tailscale.com/paths"
"tailscale.com/portlist"
"tailscale.com/posture"
@@ -378,6 +379,7 @@ type LocalBackend struct {
// c2nUpdateStatus is the status of c2n-triggered client update.
c2nUpdateStatus updateStatus
currentUser ipnauth.Actor
+ relayServer relayServer // or nil, initialized lazily
// backgroundProfileResolvers are optional background profile resolvers.
backgroundProfileResolvers set.HandleSet[profileResolver]
@@ -1135,6 +1137,10 @@ func (b *LocalBackend) Shutdown() {
b.sshServer.Shutdown()
b.sshServer = nil
}
+ if b.relayServer != nil {
+ b.relayServer.Close()
+ b.relayServer = nil
+ }
b.closePeerAPIListenersLocked()
if b.debugSink != nil {
b.e.InstallCaptureHook(nil)
@@ -4609,6 +4615,17 @@ func (b *LocalBackend) setPrefsLockedOnEntry(newp *ipn.Prefs, unlock unlockOnce)
b.sshServer = nil
}
}
+
+ if oldp.RelayServerPort().Valid() && (newp.RelayServerPort == nil ||
+ oldp.RelayServerPort().Get() != *newp.RelayServerPort) {
+ if b.relayServer != nil {
+ b.goTracker.Go(func() {
+ b.relayServer.Close()
+ })
+ b.relayServer = nil
+ }
+ }
+
if netMap != nil {
newProfile := profileFromView(netMap.UserProfiles[netMap.User()])
if newLoginName := newProfile.LoginName; newLoginName != "" {
@@ -6060,6 +6077,40 @@ func (b *LocalBackend) resetAuthURLLocked() {
b.authActor = nil
}
+// relayServer is the interface of the conditionally linked net/udprelay.Server.
+type relayServer interface {
+ // AllocateEndpoint allocates a udprelay.ServerEndpoint for the provided
+ // pair of key.DiscoPublic's. It returns an error (udprelay.ErrServerClosed)
+ // if the server has been closed.
+ AllocateEndpoint(discoA, discoB key.DiscoPublic) (udprelay.ServerEndpoint, error)
+
+ Close() error
+}
+
+type newRelayServerFunc func(port int, addrs []netip.Addr) (relayServer, int, error)
+
+var newRelayServer newRelayServerFunc // or nil
+
+func registerNewRelayServer(fn newRelayServerFunc) {
+ newRelayServer = fn
+}
+
+func (b *LocalBackend) relayServerOrInit() (_ relayServer, err error) {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ if b.relayServer != nil {
+ return b.relayServer, nil
+ }
+ if newRelayServer == nil {
+ return nil, errors.New("no relay server support")
+ }
+ b.relayServer, _, err = newRelayServer(b.Prefs().RelayServerPort().Get(), []netip.Addr{netip.MustParseAddr("127.0.0.1")})
+ if err != nil {
+ return nil, fmt.Errorf("newRelayServer: %w", err)
+ }
+ return b.relayServer, nil
+}
+
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && envknob.CanSSHD() }
// ShouldRunWebClient reports whether the web client is being run
diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go
index 21b808fd5..71c412a93 100644
--- a/ipn/ipnlocal/peerapi.go
+++ b/ipn/ipnlocal/peerapi.go
@@ -38,6 +38,7 @@ import (
"tailscale.com/net/sockstats"
"tailscale.com/tailcfg"
"tailscale.com/taildrop"
+ "tailscale.com/types/key"
"tailscale.com/types/views"
"tailscale.com/util/clientmetric"
"tailscale.com/util/httphdr"
@@ -388,6 +389,9 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
metricIngressCalls.Add(1)
h.handleServeIngress(w, r)
return
+ case "/v0/relay/endpoint":
+ h.handleServeRelayAllocateEndpoint(w, r)
+ return
}
if ph, ok := peerAPIHandlers[r.URL.Path]; ok {
ph(h, w, r)
@@ -1194,6 +1198,55 @@ func parseDriveFileExtensionForLog(path string) string {
return fileExt
}
+func (h *peerAPIHandler) handleServeRelayAllocateEndpoint(w http.ResponseWriter, r *http.Request) {
+ logAndError := func(code int, publicMsg string) {
+ h.logf("relay: error (status=%d) handling request from %v: %s", code, h.remoteAddr, publicMsg)
+ http.Error(w, publicMsg, code)
+ }
+ if !h.ps.b.ShouldRunRelayServer() {
+ logAndError(http.StatusNotFound, "relay not enabled")
+ return
+ }
+
+ if !h.PeerCaps().HasCapability(tailcfg.PeerCapabilityRelay) {
+ logAndError(http.StatusForbidden, "relay not permitted")
+ return
+ }
+
+ if r.Method != httpm.POST {
+ logAndError(http.StatusMethodNotAllowed, "only POST method is allowed")
+ return
+ }
+
+ var allocateEndpointReq struct {
+ DiscoKeys []key.DiscoPublic
+ }
+ err := json.NewDecoder(io.LimitReader(r.Body, 512)).Decode(&allocateEndpointReq)
+ if err != nil {
+ logAndError(http.StatusBadRequest, err.Error())
+ return
+ }
+ if len(allocateEndpointReq.DiscoKeys) != 2 {
+ logAndError(http.StatusBadRequest, "2 disco public keys must be supplied")
+ return
+ }
+
+ rs, err := h.ps.b.relayServerOrInit()
+ if err != nil {
+ logAndError(http.StatusInternalServerError, "error")
+ return
+ }
+ ep, err := rs.AllocateEndpoint(allocateEndpointReq.DiscoKeys[0], allocateEndpointReq.DiscoKeys[1])
+ if err != nil {
+ logAndError(http.StatusInternalServerError, err.Error())
+ return
+ }
+ err = json.NewEncoder(w).Encode(&ep)
+ if err != nil {
+ logAndError(http.StatusInternalServerError, err.Error())
+ }
+}
+
// newFakePeerAPIListener creates a new net.Listener that acts like
// it's listening on the provided IP address and on TCP port 1.
//
diff --git a/ipn/ipnlocal/relay_default.go b/ipn/ipnlocal/relay_default.go
new file mode 100644
index 000000000..b7398bee7
--- /dev/null
+++ b/ipn/ipnlocal/relay_default.go
@@ -0,0 +1,39 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !ios
+
+package ipnlocal
+
+import (
+ "net/netip"
+
+ "tailscale.com/envknob"
+ "tailscale.com/net/udprelay"
+ "tailscale.com/tailcfg"
+)
+
+func init() {
+ // Initialize the relay server constructor on all platforms except iOS (see
+ // build tag at top of file) for now as to limit the impact to binary size
+ // and resulting effect of pushing up against NetworkExtension limits.
+ // Eventually we will want to support the relay server on iOS, specifically
+ // on the Apple TV. Apple TVs are well-fitted to act as underlay relay
+ // servers as they are effectively always-on servers.
+ registerNewRelayServer(func(port int, addrs []netip.Addr) (relayServer, int, error) {
+ return udprelay.NewServer(port, addrs)
+ })
+}
+
+// ShouldRunRelayServer returns true if a relay server port has been set in prefs,
+// TAILSCALE_USE_WIP_CODE environment variable is set, and the node has the
+// tailcfg.NodeAttrRelayServer tailcfg.NodeCapability.
+//
+// TODO(jwhited): remove the envknob guard once APIs (peerapi endpoint,
+// new disco message types) are stable.
+func (b *LocalBackend) ShouldRunRelayServer() bool {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ return b.Prefs().RelayServerPort().Valid() && envknob.UseWIPCode() &&
+ b.netMap != nil && b.netMap.SelfNode.HasCap(tailcfg.NodeAttrRelayServer)
+}
diff --git a/ipn/ipnlocal/relay_ios.go b/ipn/ipnlocal/relay_ios.go
new file mode 100644
index 000000000..696f3abab
--- /dev/null
+++ b/ipn/ipnlocal/relay_ios.go
@@ -0,0 +1,10 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build ios
+
+package ipnlocal
+
+func (b *LocalBackend) ShouldRunRelayServer() bool {
+ return false
+}
diff --git a/net/udprelay/server.go b/net/udprelay/server_default.go
index 30fc08326..8a2ddeda0 100644
--- a/net/udprelay/server.go
+++ b/net/udprelay/server_default.go
@@ -1,6 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
+//go:build !ios
+
// Package udprelay contains constructs for relaying Disco and WireGuard packets
// between Tailscale clients over UDP. This package is currently considered
// experimental.
diff --git a/net/udprelay/server_test.go b/net/udprelay/server_default_test.go
index 733e50b77..0d99dc8c2 100644
--- a/net/udprelay/server_test.go
+++ b/net/udprelay/server_default_test.go
@@ -1,6 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
+//go:build !ios
+
package udprelay
import (
diff --git a/net/udprelay/server_ios.go b/net/udprelay/server_ios.go
new file mode 100644
index 000000000..07c68571e
--- /dev/null
+++ b/net/udprelay/server_ios.go
@@ -0,0 +1,8 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build ios
+
+package udprelay
+
+type ServerEndpoint struct{}