derp, derp/derphttp: add key accessors, add Client.RecvDetail

Client.RecvDetail returns a connection generation so interested clients
can detect when a reconnect happened. (Will be needed for #388)
reviewable/pr440/r1
Brad Fitzpatrick 4 years ago
parent b33c86b542
commit 4d599d194f

@ -121,8 +121,18 @@ func (s *Server) SetMeshKey(v string) {
s.meshKey = v s.meshKey = v
} }
// HasMeshKey reports whether the server is configured with a mesh key.
func (s *Server) HasMeshKey() bool { return s.meshKey != "" } func (s *Server) HasMeshKey() bool { return s.meshKey != "" }
// MeshKey returns the configured mesh key, if any.
func (s *Server) MeshKey() string { return s.meshKey }
// PrivateKey returns the server's private key.
func (s *Server) PrivateKey() key.Private { return s.privateKey }
// PublicKey returns the server's public key.
func (s *Server) PublicKey() key.Public { return s.publicKey }
// Close closes the server and waits for the connections to disconnect. // Close closes the server and waits for the connections to disconnect.
func (s *Server) Close() error { func (s *Server) Close() error {
s.mu.Lock() s.mu.Lock()

@ -55,11 +55,13 @@ type Client struct {
ctx context.Context // closed via cancelCtx in Client.Close ctx context.Context // closed via cancelCtx in Client.Close
cancelCtx context.CancelFunc cancelCtx context.CancelFunc
mu sync.Mutex mu sync.Mutex
preferred bool preferred bool
closed bool closed bool
netConn io.Closer netConn io.Closer
client *derp.Client client *derp.Client
connGen int // incremented once per new connection; valid values are >0
serverPubKey key.Public
} }
// NewRegionClient returns a new DERP-over-HTTP client. It connects lazily. // NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
@ -107,10 +109,17 @@ func NewClient(privateKey key.Private, serverURL string, logf logger.Logf) (*Cli
// Connect connects or reconnects to the server, unless already connected. // Connect connects or reconnects to the server, unless already connected.
// It returns nil if there was already a good connection, or if one was made. // It returns nil if there was already a good connection, or if one was made.
func (c *Client) Connect(ctx context.Context) error { func (c *Client) Connect(ctx context.Context) error {
_, err := c.connect(ctx, "derphttp.Client.Connect") _, _, err := c.connect(ctx, "derphttp.Client.Connect")
return err return err
} }
// ServerPublicKey returns the server's public key.
func (c *Client) ServerPublicKey() key.Public {
c.mu.Lock()
defer c.mu.Unlock()
return c.serverPubKey
}
func urlPort(u *url.URL) string { func urlPort(u *url.URL) string {
if p := u.Port(); p != "" { if p := u.Port(); p != "" {
return p return p
@ -153,14 +162,14 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
return fmt.Sprintf("https://%s/derp", node.HostName) return fmt.Sprintf("https://%s/derp", node.HostName)
} }
func (c *Client) connect(ctx context.Context, caller string) (client *derp.Client, err error) { func (c *Client) connect(ctx context.Context, caller string) (client *derp.Client, connGen int, err error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if c.closed { if c.closed {
return nil, ErrClientClosed return nil, 0, ErrClientClosed
} }
if c.client != nil { if c.client != nil {
return c.client, nil return c.client, c.connGen, nil
} }
// timeout is the fallback maximum time (if ctx doesn't limit // timeout is the fallback maximum time (if ctx doesn't limit
@ -186,7 +195,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
if c.getRegion != nil { if c.getRegion != nil {
reg = c.getRegion() reg = c.getRegion()
if reg == nil { if reg == nil {
return nil, errors.New("DERP region not available") return nil, 0, errors.New("DERP region not available")
} }
} }
@ -213,7 +222,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
tcpConn, node, err = c.dialRegion(ctx, reg) tcpConn, node, err = c.dialRegion(ctx, reg)
} }
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
// Now that we have a TCP connection, force close it if the // Now that we have a TCP connection, force close it if the
@ -251,42 +260,43 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
req, err := http.NewRequest("GET", c.urlString(node), nil) req, err := http.NewRequest("GET", c.urlString(node), nil)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
req.Header.Set("Upgrade", "DERP") req.Header.Set("Upgrade", "DERP")
req.Header.Set("Connection", "Upgrade") req.Header.Set("Connection", "Upgrade")
if err := req.Write(brw); err != nil { if err := req.Write(brw); err != nil {
return nil, err return nil, 0, err
} }
if err := brw.Flush(); err != nil { if err := brw.Flush(); err != nil {
return nil, err return nil, 0, err
} }
resp, err := http.ReadResponse(brw.Reader, req) resp, err := http.ReadResponse(brw.Reader, req)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
if resp.StatusCode != http.StatusSwitchingProtocols { if resp.StatusCode != http.StatusSwitchingProtocols {
b, _ := ioutil.ReadAll(resp.Body) b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close() resp.Body.Close()
return nil, fmt.Errorf("GET failed: %v: %s", err, b) return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
} }
derpClient, err := derp.NewMeshClient(c.privateKey, httpConn, brw, c.logf, c.MeshKey) derpClient, err := derp.NewMeshClient(c.privateKey, httpConn, brw, c.logf, c.MeshKey)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
if c.preferred { if c.preferred {
if err := derpClient.NotePreferred(true); err != nil { if err := derpClient.NotePreferred(true); err != nil {
go httpConn.Close() go httpConn.Close()
return nil, err return nil, 0, err
} }
} }
c.client = derpClient c.client = derpClient
c.netConn = tcpConn c.netConn = tcpConn
return c.client, nil c.connGen++
return c.client, c.connGen, nil
} }
func (c *Client) dialURL(ctx context.Context) (net.Conn, error) { func (c *Client) dialURL(ctx context.Context) (net.Conn, error) {
@ -464,7 +474,7 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
} }
func (c *Client) Send(dstKey key.Public, b []byte) error { func (c *Client) Send(dstKey key.Public, b []byte) error {
client, err := c.connect(context.TODO(), "derphttp.Client.Send") client, _, err := c.connect(context.TODO(), "derphttp.Client.Send")
if err != nil { if err != nil {
return err return err
} }
@ -494,7 +504,7 @@ func (c *Client) NotePreferred(v bool) {
} }
func (c *Client) WatchConnectionChanges() error { func (c *Client) WatchConnectionChanges() error {
client, err := c.connect(context.TODO(), "derphttp.Client.WatchConnectionChanges") client, _, err := c.connect(context.TODO(), "derphttp.Client.WatchConnectionChanges")
if err != nil { if err != nil {
return err return err
} }
@ -505,16 +515,25 @@ func (c *Client) WatchConnectionChanges() error {
return err return err
} }
// Recv reads a message from c. The returned message may alias the provided buffer.
// b should not be reused until the message is no longer used.
func (c *Client) Recv(b []byte) (derp.ReceivedMessage, error) { func (c *Client) Recv(b []byte) (derp.ReceivedMessage, error) {
client, err := c.connect(context.TODO(), "derphttp.Client.Recv") m, _, err := c.RecvDetail(b)
return m, err
}
// RecvDetail is like Recv, but additional returns the connection generation on each message.
// The connGen value is incremented every time the derphttp.Client reconnects to the server.
func (c *Client) RecvDetail(b []byte) (m derp.ReceivedMessage, connGen int, err error) {
client, connGen, err := c.connect(context.TODO(), "derphttp.Client.Recv")
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
m, err := client.Recv(b) m, err = client.Recv(b)
if err != nil { if err != nil {
c.closeForReconnect(client) c.closeForReconnect(client)
} }
return m, err return m, connGen, err
} }
// Close closes the client. It will not automatically reconnect after // Close closes the client. It will not automatically reconnect after

Loading…
Cancel
Save