mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-24 09:51:41 +00:00
feature/{condregister,relayserver}: implement the skeleton for the relayserver feature (#15699)
This feature is "registered" as an ipnlocal.Extension, and conditionally linked depending on GOOS and ts_omit_relayserver build tag. The feature is not linked on iOS in attempt 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. This skeleton begins to tie a PeerAPI endpoint to a net/udprelay.Server. The PeerAPI endpoint is currently no-op as extension.shouldRunRelayServer() always returns false. Follow-up commits will implement extension.shouldRunRelayServer(). Updates tailscale/corp#27502 Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
parent
450bcbcb08
commit
37f5fd2ec1
@ -41,7 +41,7 @@ while [ "$#" -gt 1 ]; do
|
||||
fi
|
||||
shift
|
||||
ldflags="$ldflags -w -s"
|
||||
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion,ts_omit_ssh,ts_omit_wakeonlan,ts_omit_capture"
|
||||
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion,ts_omit_ssh,ts_omit_wakeonlan,ts_omit_capture,ts_omit_relayserver"
|
||||
;;
|
||||
--box)
|
||||
if [ ! -z "${TAGS:-}" ]; then
|
||||
|
@ -806,6 +806,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/feature from tailscale.com/feature/wakeonlan+
|
||||
tailscale.com/feature/capture from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/condregister from tailscale.com/tsnet
|
||||
tailscale.com/feature/relayserver from tailscale.com/feature/condregister
|
||||
L tailscale.com/feature/tap from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister
|
||||
tailscale.com/health from tailscale.com/control/controlclient+
|
||||
@ -816,7 +817,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/ipn from tailscale.com/client/local+
|
||||
tailscale.com/ipn/conffile from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+
|
||||
tailscale.com/ipn/ipnstate from tailscale.com/client/local+
|
||||
tailscale.com/ipn/localapi from tailscale.com/tsnet+
|
||||
@ -883,6 +884,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/feature/relayserver
|
||||
tailscale.com/omit from tailscale.com/ipn/conffile
|
||||
tailscale.com/paths from tailscale.com/client/local+
|
||||
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
|
@ -264,6 +264,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/feature from tailscale.com/feature/wakeonlan+
|
||||
tailscale.com/feature/capture from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/feature/relayserver from tailscale.com/feature/condregister
|
||||
L tailscale.com/feature/tap from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister
|
||||
tailscale.com/health from tailscale.com/control/controlclient+
|
||||
@ -334,6 +335,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/feature/relayserver
|
||||
tailscale.com/omit from tailscale.com/ipn/conffile
|
||||
tailscale.com/paths from tailscale.com/client/local+
|
||||
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
|
8
feature/condregister/maybe_relayserver.go
Normal file
8
feature/condregister/maybe_relayserver.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !ts_omit_relayserver
|
||||
|
||||
package condregister
|
||||
|
||||
import _ "tailscale.com/feature/relayserver"
|
154
feature/relayserver/relayserver.go
Normal file
154
feature/relayserver/relayserver.go
Normal file
@ -0,0 +1,154 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package relayserver registers the relay server feature and implements its
|
||||
// associated ipnext.Extension.
|
||||
package relayserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/ipn/ipnext"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/net/udprelay"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/httpm"
|
||||
)
|
||||
|
||||
// featureName is the name of the feature implemented by this package.
|
||||
// It is also the [extension] name and the log prefix.
|
||||
const featureName = "relayserver"
|
||||
|
||||
func init() {
|
||||
feature.Register(featureName)
|
||||
ipnext.RegisterExtension(featureName, newExtension)
|
||||
ipnlocal.RegisterPeerAPIHandler("/v0/relay/endpoint", handlePeerAPIRelayAllocateEndpoint)
|
||||
}
|
||||
|
||||
// newExtension is an [ipnext.NewExtensionFn] that creates a new relay server
|
||||
// extension. It is registered with [ipnext.RegisterExtension] if the package is
|
||||
// imported.
|
||||
func newExtension(logf logger.Logf, _ *tsd.System) (ipnext.Extension, error) {
|
||||
return &extension{logf: logger.WithPrefix(logf, featureName+": ")}, nil
|
||||
}
|
||||
|
||||
// extension is an [ipnext.Extension] managing the relay server on platforms
|
||||
// that import this package.
|
||||
type extension struct {
|
||||
logf logger.Logf
|
||||
|
||||
mu sync.Mutex // guards the following fields
|
||||
shutdown bool
|
||||
port int
|
||||
server *udprelay.Server // lazily initialized
|
||||
}
|
||||
|
||||
// Name implements [ipnext.Extension].
|
||||
func (e *extension) Name() string {
|
||||
return featureName
|
||||
}
|
||||
|
||||
// Init implements [ipnext.Extension] by registering callbacks and providers
|
||||
// for the duration of the extension's lifetime.
|
||||
func (e *extension) Init(_ ipnext.Host) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown implements [ipnlocal.Extension].
|
||||
func (e *extension) Shutdown() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.shutdown = true
|
||||
if e.server != nil {
|
||||
e.server.Close()
|
||||
e.server = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *extension) shouldRunRelayServer() bool {
|
||||
// TODO(jwhited): consider:
|
||||
// 1. tailcfg.NodeAttrRelayServer
|
||||
// 2. ipn.Prefs.RelayServerPort
|
||||
// 3. envknob.UseWIPCode()
|
||||
// 4. e.shutdown
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *extension) relayServerOrInit() (*udprelay.Server, error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if e.shutdown {
|
||||
return nil, errors.New("relay server is shutdown")
|
||||
}
|
||||
if e.server != nil {
|
||||
return e.server, nil
|
||||
}
|
||||
var err error
|
||||
e.server, _, err = udprelay.NewServer(e.port, []netip.Addr{netip.MustParseAddr("127.0.0.1")})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.server, nil
|
||||
}
|
||||
|
||||
func handlePeerAPIRelayAllocateEndpoint(h ipnlocal.PeerAPIHandler, w http.ResponseWriter, r *http.Request) {
|
||||
// TODO(jwhited): log errors
|
||||
e, ok := h.LocalBackend().FindExtensionByName(featureName).(*extension)
|
||||
if !ok {
|
||||
http.Error(w, "relay failed to initialize", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
if !e.shouldRunRelayServer() {
|
||||
http.Error(w, "relay not enabled", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !h.PeerCaps().HasCapability(tailcfg.PeerCapabilityRelay) {
|
||||
http.Error(w, "relay not permitted", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method != httpm.POST {
|
||||
http.Error(w, "only POST method is allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var allocateEndpointReq struct {
|
||||
DiscoKeys []key.DiscoPublic
|
||||
}
|
||||
err := json.NewDecoder(io.LimitReader(r.Body, 512)).Decode(&allocateEndpointReq)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(allocateEndpointReq.DiscoKeys) != 2 {
|
||||
http.Error(w, "2 disco public keys must be supplied", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rs, err := e.relayServerOrInit()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ep, err := rs.AllocateEndpoint(allocateEndpointReq.DiscoKeys[0], allocateEndpointReq.DiscoKeys[1])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(&ep)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user