|
|
|
@ -55,11 +55,13 @@ type Client struct {
|
|
|
|
|
ctx context.Context // closed via cancelCtx in Client.Close
|
|
|
|
|
cancelCtx context.CancelFunc
|
|
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
preferred bool
|
|
|
|
|
closed bool
|
|
|
|
|
netConn io.Closer
|
|
|
|
|
client *derp.Client
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
preferred bool
|
|
|
|
|
closed bool
|
|
|
|
|
netConn io.Closer
|
|
|
|
|
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.
|
|
|
|
@ -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.
|
|
|
|
|
// It returns nil if there was already a good connection, or if one was made.
|
|
|
|
|
func (c *Client) Connect(ctx context.Context) error {
|
|
|
|
|
_, err := c.connect(ctx, "derphttp.Client.Connect")
|
|
|
|
|
_, _, err := c.connect(ctx, "derphttp.Client.Connect")
|
|
|
|
|
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 {
|
|
|
|
|
if p := u.Port(); p != "" {
|
|
|
|
|
return p
|
|
|
|
@ -153,14 +162,14 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
|
|
|
|
|
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()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
if c.closed {
|
|
|
|
|
return nil, ErrClientClosed
|
|
|
|
|
return nil, 0, ErrClientClosed
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
@ -186,7 +195,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
|
|
|
|
if c.getRegion != nil {
|
|
|
|
|
reg = c.getRegion()
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
req.Header.Set("Upgrade", "DERP")
|
|
|
|
|
req.Header.Set("Connection", "Upgrade")
|
|
|
|
|
|
|
|
|
|
if err := req.Write(brw); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
if err := brw.Flush(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp, err := http.ReadResponse(brw.Reader, req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
if resp.StatusCode != http.StatusSwitchingProtocols {
|
|
|
|
|
b, _ := ioutil.ReadAll(resp.Body)
|
|
|
|
|
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)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
if c.preferred {
|
|
|
|
|
if err := derpClient.NotePreferred(true); err != nil {
|
|
|
|
|
go httpConn.Close()
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.client = derpClient
|
|
|
|
|
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) {
|
|
|
|
@ -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 {
|
|
|
|
|
client, err := c.connect(context.TODO(), "derphttp.Client.Send")
|
|
|
|
|
client, _, err := c.connect(context.TODO(), "derphttp.Client.Send")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@ -494,7 +504,7 @@ func (c *Client) NotePreferred(v bool) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@ -505,16 +515,25 @@ func (c *Client) WatchConnectionChanges() error {
|
|
|
|
|
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) {
|
|
|
|
|
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 {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
m, err := client.Recv(b)
|
|
|
|
|
m, err = client.Recv(b)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.closeForReconnect(client)
|
|
|
|
|
}
|
|
|
|
|
return m, err
|
|
|
|
|
return m, connGen, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close closes the client. It will not automatically reconnect after
|
|
|
|
|