mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
wgengine/magicsock, derp, derp/derphttp: respond to DERP server->client pings
No server support yet, but we want Tailscale 1.6 clients to be able to respond to them when the server can do it. Updates #1310 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
66480755c2
commit
79d8288f0a
@ -59,7 +59,8 @@
|
|||||||
* server sends frameServerInfo
|
* server sends frameServerInfo
|
||||||
|
|
||||||
Steady state:
|
Steady state:
|
||||||
* server occasionally sends frameKeepAlive
|
* server occasionally sends frameKeepAlive (or framePing)
|
||||||
|
* client responds to any framePing with a framePong
|
||||||
* client sends frameSendPacket
|
* client sends frameSendPacket
|
||||||
* server then sends frameRecvPacket to recipient
|
* server then sends frameRecvPacket to recipient
|
||||||
*/
|
*/
|
||||||
@ -97,6 +98,9 @@
|
|||||||
// connection. (To be used for cluster load balancing
|
// connection. (To be used for cluster load balancing
|
||||||
// purposes, when clients end up on a non-ideal node)
|
// purposes, when clients end up on a non-ideal node)
|
||||||
frameClosePeer = frameType(0x11) // 32B pub key of peer to close.
|
frameClosePeer = frameType(0x11) // 32B pub key of peer to close.
|
||||||
|
|
||||||
|
framePing = frameType(0x12) // 8 byte ping payload, to be echoed back in framePong
|
||||||
|
framePong = frameType(0x13) // 8 byte payload, the contents of the ping being replied to
|
||||||
)
|
)
|
||||||
|
|
||||||
var bin = binary.BigEndian
|
var bin = binary.BigEndian
|
||||||
|
@ -238,6 +238,18 @@ func (c *Client) ForwardPacket(srcKey, dstKey key.Public, pkt []byte) (err error
|
|||||||
|
|
||||||
func (c *Client) writeTimeoutFired() { c.nc.Close() }
|
func (c *Client) writeTimeoutFired() { c.nc.Close() }
|
||||||
|
|
||||||
|
func (c *Client) SendPong(data [8]byte) error {
|
||||||
|
c.wmu.Lock()
|
||||||
|
defer c.wmu.Unlock()
|
||||||
|
if err := writeFrameHeader(c.bw, framePong, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := c.bw.Write(data[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.bw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// NotePreferred sends a packet that tells the server whether this
|
// NotePreferred sends a packet that tells the server whether this
|
||||||
// client is the user's preferred server. This is only used in the
|
// client is the user's preferred server. This is only used in the
|
||||||
// server for stats.
|
// server for stats.
|
||||||
@ -319,6 +331,12 @@ type ServerInfoMessage struct{
|
|||||||
|
|
||||||
func (ServerInfoMessage) msg() {}
|
func (ServerInfoMessage) msg() {}
|
||||||
|
|
||||||
|
// PingMessage is a request from a client or server to reply to the
|
||||||
|
// other side with a PongMessage with the given payload.
|
||||||
|
type PingMessage [8]byte
|
||||||
|
|
||||||
|
func (PingMessage) msg() {}
|
||||||
|
|
||||||
// Recv reads a message from the DERP server.
|
// Recv reads a message from the DERP server.
|
||||||
//
|
//
|
||||||
// The returned message may alias memory owned by the Client; it
|
// The returned message may alias memory owned by the Client; it
|
||||||
@ -397,8 +415,8 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
|
|||||||
// TODO: add the results of parseServerInfo to ServerInfoMessage if we ever need it.
|
// TODO: add the results of parseServerInfo to ServerInfoMessage if we ever need it.
|
||||||
return ServerInfoMessage{}, nil
|
return ServerInfoMessage{}, nil
|
||||||
case frameKeepAlive:
|
case frameKeepAlive:
|
||||||
// TODO: eventually we'll have server->client pings that
|
// A one-way keep-alive message that doesn't require an acknowledgement.
|
||||||
// require ack pongs.
|
// This predated framePing/framePong.
|
||||||
continue
|
continue
|
||||||
case framePeerGone:
|
case framePeerGone:
|
||||||
if n < keyLen {
|
if n < keyLen {
|
||||||
@ -427,6 +445,15 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
|
|||||||
copy(rp.Source[:], b[:keyLen])
|
copy(rp.Source[:], b[:keyLen])
|
||||||
rp.Data = b[keyLen:n]
|
rp.Data = b[keyLen:n]
|
||||||
return rp, nil
|
return rp, nil
|
||||||
|
|
||||||
|
case framePing:
|
||||||
|
var pm PingMessage
|
||||||
|
if n < 8 {
|
||||||
|
c.logf("[unexpected] dropping short ping frame")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
copy(pm[:], b[:])
|
||||||
|
return pm, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@ -791,6 +792,63 @@ func TestMetaCert(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyNetConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dummyNetConn) SetReadDeadline(time.Time) error { return nil }
|
||||||
|
|
||||||
|
func TestClientRecv(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
want interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ping",
|
||||||
|
input: []byte{
|
||||||
|
byte(framePing), 0, 0, 0, 8,
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8,
|
||||||
|
},
|
||||||
|
want: PingMessage{1, 2, 3, 4, 5, 6, 7, 8},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Client{
|
||||||
|
nc: dummyNetConn{},
|
||||||
|
br: bufio.NewReader(bytes.NewReader(tt.input)),
|
||||||
|
logf: t.Logf,
|
||||||
|
}
|
||||||
|
got, err := c.Recv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("got %#v; want %#v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientSendPong(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := &Client{
|
||||||
|
bw: bufio.NewWriter(&buf),
|
||||||
|
}
|
||||||
|
if err := c.SendPong([8]byte{1, 2, 3, 4, 5, 6, 7, 8}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := []byte{
|
||||||
|
byte(framePong), 0, 0, 0, 8,
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8,
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), want) {
|
||||||
|
t.Errorf("unexpected output\nwrote: % 02x\n want: % 02x", buf.Bytes(), want)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkSendRecv(b *testing.B) {
|
func BenchmarkSendRecv(b *testing.B) {
|
||||||
for _, size := range []int{10, 100, 1000, 10000} {
|
for _, size := range []int{10, 100, 1000, 10000} {
|
||||||
b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) })
|
b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) })
|
||||||
|
@ -642,6 +642,29 @@ func (c *Client) ForwardPacket(from, to key.Public, b []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendPong sends a reply to a ping, with the ping's provided
|
||||||
|
// challenge/identifier data.
|
||||||
|
//
|
||||||
|
// Unlike other send methods, SendPong makes no attempt to connect or
|
||||||
|
// reconnect to the peer. It's best effort. If there's a connection
|
||||||
|
// problem, the server will choose to hang up on us if we're not
|
||||||
|
// replying.
|
||||||
|
func (c *Client) SendPong(data [8]byte) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.closed {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return ErrClientClosed
|
||||||
|
}
|
||||||
|
if c.client == nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return errors.New("not connected")
|
||||||
|
}
|
||||||
|
dc := c.client
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
return dc.SendPong(data)
|
||||||
|
}
|
||||||
|
|
||||||
// NotePreferred notes whether this Client is the caller's preferred
|
// NotePreferred notes whether this Client is the caller's preferred
|
||||||
// (home) DERP node. It's only used for stats.
|
// (home) DERP node. It's only used for stats.
|
||||||
func (c *Client) NotePreferred(v bool) {
|
func (c *Client) NotePreferred(v bool) {
|
||||||
|
@ -1465,6 +1465,15 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d
|
|||||||
peerPresent[m.Source] = true
|
peerPresent[m.Source] = true
|
||||||
c.addDerpPeerRoute(m.Source, regionID, dc)
|
c.addDerpPeerRoute(m.Source, regionID, dc)
|
||||||
}
|
}
|
||||||
|
case derp.PingMessage:
|
||||||
|
// Best effort reply to the ping.
|
||||||
|
pingData := [8]byte(m)
|
||||||
|
go func() {
|
||||||
|
if err := dc.SendPong(pingData); err != nil {
|
||||||
|
c.logf("magicsock: derp-%d SendPong error: %v", regionID, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
continue
|
||||||
default:
|
default:
|
||||||
// Ignore.
|
// Ignore.
|
||||||
continue
|
continue
|
||||||
|
Loading…
Reference in New Issue
Block a user