mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-08 09:07:44 +00:00
control/controlclient: add Noise client
Updates #3488 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
26f27a620a
commit
0f31a0fc76
@ -170,7 +170,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/client/tailscale from tailscale.com/derp
|
tailscale.com/client/tailscale from tailscale.com/derp
|
||||||
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
|
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
|
||||||
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled+
|
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled+
|
||||||
|
tailscale.com/control/controlbase from tailscale.com/control/controlclient+
|
||||||
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
|
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
|
||||||
|
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
||||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/derp from tailscale.com/derp/derphttp+
|
tailscale.com/derp from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscaled+
|
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscaled+
|
||||||
@ -276,7 +278,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
|
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
|
||||||
golang.org/x/crypto/acme from tailscale.com/ipn/localapi
|
golang.org/x/crypto/acme from tailscale.com/ipn/localapi
|
||||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
|
||||||
golang.org/x/crypto/blake2s from golang.zx2c4.com/wireguard/device
|
golang.org/x/crypto/blake2s from golang.zx2c4.com/wireguard/device+
|
||||||
LD golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
|
LD golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
|
||||||
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305+
|
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305+
|
||||||
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
|
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
|
||||||
@ -284,7 +286,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||||
golang.org/x/crypto/curve25519 from crypto/tls+
|
golang.org/x/crypto/curve25519 from crypto/tls+
|
||||||
LD golang.org/x/crypto/ed25519 from golang.org/x/crypto/ssh
|
LD golang.org/x/crypto/ed25519 from golang.org/x/crypto/ssh
|
||||||
golang.org/x/crypto/hkdf from crypto/tls
|
golang.org/x/crypto/hkdf from crypto/tls+
|
||||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||||
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
||||||
@ -303,7 +305,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||||
D golang.org/x/net/route from net+
|
D golang.org/x/net/route from net+
|
||||||
golang.org/x/sync/errgroup from github.com/tailscale/goupnp/httpu+
|
golang.org/x/sync/errgroup from github.com/tailscale/goupnp/httpu+
|
||||||
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
|
golang.org/x/sync/singleflight from tailscale.com/net/dnscache+
|
||||||
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
||||||
LD golang.org/x/sys/unix from github.com/insomniacslk/dhcp/interfaces+
|
LD golang.org/x/sys/unix from github.com/insomniacslk/dhcp/interfaces+
|
||||||
W golang.org/x/sys/windows from github.com/go-ole/go-ole+
|
W golang.org/x/sys/windows from github.com/go-ole/go-ole+
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/control/controlknobs"
|
"tailscale.com/control/controlknobs"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
@ -73,6 +74,9 @@ type Direct struct {
|
|||||||
serverKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key
|
serverKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key
|
||||||
serverNoiseKey key.MachinePublic
|
serverNoiseKey key.MachinePublic
|
||||||
|
|
||||||
|
sfGroup singleflight.Group // protects noiseClient creation.
|
||||||
|
noiseClient *noiseClient
|
||||||
|
|
||||||
persist persist.Persist
|
persist persist.Persist
|
||||||
authKey string
|
authKey string
|
||||||
tryingNewKey key.NodePrivate
|
tryingNewKey key.NodePrivate
|
||||||
@ -203,6 +207,19 @@ func NewDirect(opts Options) (*Direct, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying Noise connection(s).
|
||||||
|
func (c *Direct) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if c.noiseClient != nil {
|
||||||
|
if err := c.noiseClient.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.noiseClient = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetHostinfo clones the provided Hostinfo and remembers it for the
|
// SetHostinfo clones the provided Hostinfo and remembers it for the
|
||||||
// next update. It reports whether the Hostinfo has changed.
|
// next update. It reports whether the Hostinfo has changed.
|
||||||
func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool {
|
func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool {
|
||||||
@ -1204,6 +1221,39 @@ func sleepAsRequested(ctx context.Context, logf logger.Logf, timeoutReset chan<-
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getNoiseClient returns the noise client, creating one if one doesn't exist.
|
||||||
|
func (c *Direct) getNoiseClient() (*noiseClient, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
serverNoiseKey := c.serverNoiseKey
|
||||||
|
nc := c.noiseClient
|
||||||
|
c.mu.Unlock()
|
||||||
|
if serverNoiseKey.IsZero() {
|
||||||
|
return nil, errors.New("zero serverNoiseKey")
|
||||||
|
}
|
||||||
|
if nc != nil {
|
||||||
|
return nc, nil
|
||||||
|
}
|
||||||
|
np, err, _ := c.sfGroup.Do("noise", func() (interface{}, error) {
|
||||||
|
k, err := c.getMachinePrivKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nc, err = newNoiseClient(k, serverNoiseKey, c.serverURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.noiseClient = nc
|
||||||
|
return nc, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return np.(*noiseClient), nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetDNS sends the SetDNSRequest request to the control plane server,
|
// SetDNS sends the SetDNSRequest request to the control plane server,
|
||||||
// requesting a DNS record be created or updated.
|
// requesting a DNS record be created or updated.
|
||||||
func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err error) {
|
func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err error) {
|
||||||
|
141
control/controlclient/noise.go
Normal file
141
control/controlclient/noise.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package controlclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"tailscale.com/control/controlbase"
|
||||||
|
"tailscale.com/control/controlhttp"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
"tailscale.com/util/multierr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// noiseConn is a wrapper around controlbase.Conn.
|
||||||
|
// It allows attaching an ID to a connection to allow
|
||||||
|
// cleaning up references in the pool when the connection
|
||||||
|
// is closed.
|
||||||
|
type noiseConn struct {
|
||||||
|
*controlbase.Conn
|
||||||
|
id int
|
||||||
|
pool *noiseClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *noiseConn) Close() error {
|
||||||
|
if err := c.Conn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.pool.connClosed(c.id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// noiseClient provides a http.Client to connect to tailcontrol over
|
||||||
|
// the ts2021 protocol.
|
||||||
|
type noiseClient struct {
|
||||||
|
*http.Client // HTTP client used to talk to tailcontrol
|
||||||
|
privKey key.MachinePrivate
|
||||||
|
serverPubKey key.MachinePublic
|
||||||
|
serverHost string // the host:port part of serverURL
|
||||||
|
|
||||||
|
// mu only protects the following variables.
|
||||||
|
mu sync.Mutex
|
||||||
|
nextID int
|
||||||
|
connPool map[int]*noiseConn // active connections not yet closed; see noiseConn.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// newNoiseClient returns a new noiseClient for the provided server and machine key.
|
||||||
|
// serverURL is of the form https://<host>:<port> (no trailing slash).
|
||||||
|
func newNoiseClient(priKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string) (*noiseClient, error) {
|
||||||
|
u, err := url.Parse(serverURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var host string
|
||||||
|
if u.Port() != "" {
|
||||||
|
// If there is an explicit port specified use it.
|
||||||
|
host = u.Host
|
||||||
|
} else {
|
||||||
|
// Otherwise, controlhttp.Dial expects an http endpoint.
|
||||||
|
host = fmt.Sprintf("%v:80", u.Hostname())
|
||||||
|
}
|
||||||
|
np := &noiseClient{
|
||||||
|
serverPubKey: serverPubKey,
|
||||||
|
privKey: priKey,
|
||||||
|
serverHost: host,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new http.Client which dials out using nc.Dial.
|
||||||
|
np.Client = &http.Client{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
ReadIdleTimeout: time.Minute,
|
||||||
|
DialTLS: np.dial,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return np, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connClosed removes the connection with the provided ID from the pool
|
||||||
|
// of active connections.
|
||||||
|
func (nc *noiseClient) connClosed(id int) {
|
||||||
|
nc.mu.Lock()
|
||||||
|
defer nc.mu.Unlock()
|
||||||
|
delete(nc.connPool, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes all the underlying noise connections.
|
||||||
|
// It is a no-op and returns nil if the connection is already closed.
|
||||||
|
func (nc *noiseClient) Close() error {
|
||||||
|
nc.mu.Lock()
|
||||||
|
conns := nc.connPool
|
||||||
|
nc.connPool = nil
|
||||||
|
nc.mu.Unlock()
|
||||||
|
|
||||||
|
var errors []error
|
||||||
|
for _, c := range conns {
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return multierr.New(errors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dial opens a new connection to tailcontrol, fetching the server noise key
|
||||||
|
// if not cached. It implements the signature needed by http2.Transport.DialTLS
|
||||||
|
// but ignores all params as it only dials out to the server the noiseClient was
|
||||||
|
// created for.
|
||||||
|
func (nc *noiseClient) dial(_, _ string, _ *tls.Config) (net.Conn, error) {
|
||||||
|
nc.mu.Lock()
|
||||||
|
connID := nc.nextID
|
||||||
|
if nc.connPool == nil {
|
||||||
|
nc.connPool = make(map[int]*noiseConn)
|
||||||
|
}
|
||||||
|
nc.nextID++
|
||||||
|
nc.mu.Unlock()
|
||||||
|
|
||||||
|
// Timeout is a little arbitrary, but plenty long enough for even the
|
||||||
|
// highest latency links.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn, err := controlhttp.Dial(ctx, nc.serverHost, nc.privKey, nc.serverPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nc.mu.Lock()
|
||||||
|
defer nc.mu.Unlock()
|
||||||
|
ncc := &noiseConn{Conn: conn, id: connID, pool: nc}
|
||||||
|
nc.connPool[ncc.id] = ncc
|
||||||
|
return ncc, nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user