client_js.go 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. // Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package controlhttp
  5. import (
  6. "context"
  7. "encoding/base64"
  8. "net"
  9. "net/url"
  10. "nhooyr.io/websocket"
  11. "tailscale.com/control/controlbase"
  12. "tailscale.com/net/dnscache"
  13. "tailscale.com/types/key"
  14. )
  15. // Variant of Dial that tunnels the request over WebSockets, since we cannot do
  16. // bi-directional communication over an HTTP connection when in JS.
  17. func Dial(ctx context.Context, host string, httpPort string, httpsPort string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
  18. init, cont, err := controlbase.ClientDeferred(machineKey, controlKey, protocolVersion)
  19. if err != nil {
  20. return nil, err
  21. }
  22. wsScheme := "wss"
  23. if host == "localhost" {
  24. wsScheme = "ws"
  25. host = net.JoinHostPort(host, httpPort)
  26. }
  27. wsURL := &url.URL{
  28. Scheme: wsScheme,
  29. Host: host,
  30. Path: serverUpgradePath,
  31. // Can't set HTTP headers on the websocket request, so we have to to send
  32. // the handshake via an HTTP header.
  33. RawQuery: url.Values{
  34. handshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
  35. }.Encode(),
  36. }
  37. wsConn, _, err := websocket.Dial(ctx, wsURL.String(), &websocket.DialOptions{
  38. Subprotocols: []string{upgradeHeaderValue},
  39. })
  40. if err != nil {
  41. return nil, err
  42. }
  43. netConn := websocket.NetConn(context.Background(), wsConn, websocket.MessageBinary)
  44. cbConn, err := cont(ctx, netConn)
  45. if err != nil {
  46. netConn.Close()
  47. return nil, err
  48. }
  49. return cbConn, nil
  50. }