wgengine/magicsock: open discovery naclbox messages from known peers
And track known peers. Doesn't yet do anything with the messages. (nor does it send any yet) Start of docs on the message format. More will come in subsequent changes. Updates #483
This commit is contained in:
parent
9258d64261
commit
103c06cc68
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/tailscale/wireguard-go/conn"
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/wgcfg"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"golang.org/x/time/rate"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/control/controlclient"
|
||||
|
@ -76,6 +77,7 @@ type Conn struct {
|
|||
udpRecvCh chan udpReadResult
|
||||
derpRecvCh chan derpReadResult
|
||||
|
||||
// ============================================================
|
||||
mu sync.Mutex // guards all following fields
|
||||
|
||||
started bool
|
||||
|
@ -88,6 +90,8 @@ type Conn struct {
|
|||
peerSet map[key.Public]struct{}
|
||||
|
||||
discoPrivate key.Private
|
||||
nodeOfDisco map[tailcfg.DiscoKey]tailcfg.NodeKey
|
||||
discoOfNode map[tailcfg.NodeKey]tailcfg.DiscoKey
|
||||
|
||||
// addrsByUDP is a map of every remote ip:port to a priority
|
||||
// list of endpoint addresses for a peer.
|
||||
|
@ -1164,6 +1168,9 @@ func (c *Conn) awaitUDP4(b []byte) {
|
|||
c.stunReceiveFunc.Load().(func([]byte, *net.UDPAddr))(b[:n], addr)
|
||||
continue
|
||||
}
|
||||
if c.handleDiscoMessage(b[:n], addr) {
|
||||
continue
|
||||
}
|
||||
|
||||
addr.IP = addr.IP.To4()
|
||||
select {
|
||||
|
@ -1277,6 +1284,55 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, *net.UDPAddr, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// handleDiscoMessage reports whether msg was a Tailscale inter-node discovery message
|
||||
// that was handled.
|
||||
//
|
||||
// A discovery message has the form:
|
||||
//
|
||||
// * magic [6]byte
|
||||
// * senderDiscoPubKey [32]byte
|
||||
// * nonce [24]byte
|
||||
// * naclbox of payload
|
||||
func (c *Conn) handleDiscoMessage(msg []byte, addr *net.UDPAddr) bool {
|
||||
const magic = "TS💬"
|
||||
const nonceLen = 24
|
||||
const headerLen = len(magic) + len(tailcfg.DiscoKey{}) + nonceLen
|
||||
if len(msg) < headerLen || string(msg[:len(magic)]) != magic {
|
||||
return false
|
||||
}
|
||||
var sender tailcfg.DiscoKey
|
||||
copy(sender[:], msg[len(magic):])
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.discoPrivate.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
senderNodeKey, ok := c.nodeOfDisco[sender]
|
||||
if !ok {
|
||||
// Returning false keeps passing it down, to WireGuard.
|
||||
// WireGuard will almost surely reject it, but give it a chance.
|
||||
return false
|
||||
}
|
||||
|
||||
// First, do we even know (and thus care) about this sender? If not,
|
||||
// don't bother decrypting it.
|
||||
|
||||
var nonce [nonceLen]byte
|
||||
copy(nonce[:], msg[len(magic)+len(key.Public{}):])
|
||||
sealedBox := msg[headerLen:]
|
||||
payload, ok := box.Open(nil, sealedBox, &nonce, key.Public(sender).B32(), c.discoPrivate.B32())
|
||||
if !ok {
|
||||
c.logf("magicsock: failed to open disco message box purportedly from %s (disco key %x)", senderNodeKey.ShortString(), sender[:])
|
||||
return false
|
||||
}
|
||||
|
||||
c.logf("magicsock: got disco message from %s: %x (%q)", senderNodeKey.ShortString(), payload, payload)
|
||||
return true
|
||||
}
|
||||
|
||||
// SetPrivateKey sets the connection's private key.
|
||||
//
|
||||
// This is only used to be able prove our identity when connecting to
|
||||
|
@ -1368,11 +1424,39 @@ func (c *Conn) SetNetworkMap(nm *controlclient.NetworkMap) {
|
|||
if reflect.DeepEqual(nm, c.netMap) {
|
||||
return
|
||||
}
|
||||
c.logf("magicsock: got updated network map")
|
||||
|
||||
numDisco := 0
|
||||
for _, n := range nm.Peers {
|
||||
if !n.DiscoKey.IsZero() {
|
||||
numDisco++
|
||||
}
|
||||
}
|
||||
|
||||
c.logf("magicsock: got updated network map; %d peers (%d with discokey)", len(nm.Peers), numDisco)
|
||||
c.netMap = nm
|
||||
// TODO: look at Debug fields
|
||||
// TODO: look at DiscoKey fields to reset AddrSet states when node restarts
|
||||
|
||||
// Build and/or update node<->disco maps, only reallocating if
|
||||
// the set of discokeys changed.
|
||||
for pass := 1; pass <= 2; pass++ {
|
||||
if c.nodeOfDisco == nil || pass == 2 {
|
||||
c.nodeOfDisco = map[tailcfg.DiscoKey]tailcfg.NodeKey{}
|
||||
c.discoOfNode = map[tailcfg.NodeKey]tailcfg.DiscoKey{}
|
||||
}
|
||||
for _, n := range nm.Peers {
|
||||
if !n.DiscoKey.IsZero() {
|
||||
c.nodeOfDisco[n.DiscoKey] = n.Key
|
||||
if old, ok := c.discoOfNode[n.Key]; ok && old != n.DiscoKey {
|
||||
c.logf("magicsock: node %s changed discovery key from %x to %x", n.Key.ShortString(), old[:8], n.DiscoKey[:8])
|
||||
// TODO: reset AddrSet states, reset wireguard session key, etc.
|
||||
}
|
||||
c.discoOfNode[n.Key] = n.DiscoKey
|
||||
}
|
||||
}
|
||||
if len(c.nodeOfDisco) == numDisco && len(c.discoOfNode) == numDisco {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *Conn) wantDerpLocked() bool { return c.derpMap != nil }
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun/tuntest"
|
||||
"github.com/tailscale/wireguard-go/wgcfg"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/derp/derphttp"
|
||||
"tailscale.com/derp/derpmap"
|
||||
|
@ -835,3 +836,30 @@ func TestAddrSet(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoMessage(t *testing.T) {
|
||||
peer1Priv := key.NewPrivate()
|
||||
peer1Pub := peer1Priv.Public()
|
||||
|
||||
c := &Conn{
|
||||
logf: t.Logf,
|
||||
discoPrivate: key.NewPrivate(),
|
||||
nodeOfDisco: map[tailcfg.DiscoKey]tailcfg.NodeKey{
|
||||
tailcfg.DiscoKey(peer1Pub): tailcfg.NodeKey{1: 1},
|
||||
},
|
||||
}
|
||||
|
||||
const payload = "why hello"
|
||||
|
||||
var nonce [24]byte
|
||||
crand.Read(nonce[:])
|
||||
|
||||
pkt := append([]byte("TS💬"), peer1Pub[:]...)
|
||||
pkt = append(pkt, nonce[:]...)
|
||||
|
||||
pkt = box.Seal(pkt, []byte(payload), &nonce, c.discoPrivate.Public().B32(), peer1Priv.B32())
|
||||
got := c.handleDiscoMessage(pkt, &net.UDPAddr{})
|
||||
if !got {
|
||||
t.Error("failed to open it")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue