| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162 |
- // Copyright (c) Tailscale Inc & contributors
- // SPDX-License-Identifier: BSD-3-Clause
- package controlhttp
- import (
- "context"
- "encoding/base64"
- "errors"
- "net"
- "net/url"
- "github.com/coder/websocket"
- "tailscale.com/control/controlbase"
- "tailscale.com/control/controlhttp/controlhttpcommon"
- "tailscale.com/net/wsconn"
- )
- // Variant of Dial that tunnels the request over WebSockets, since we cannot do
- // bi-directional communication over an HTTP connection when in JS.
- func (d *Dialer) Dial(ctx context.Context) (*ClientConn, error) {
- if d.Hostname == "" {
- return nil, errors.New("required Dialer.Hostname empty")
- }
- init, cont, err := controlbase.ClientDeferred(d.MachineKey, d.ControlKey, d.ProtocolVersion)
- if err != nil {
- return nil, err
- }
- wsScheme := "wss"
- host := d.Hostname
- // If using a custom control server (on a non-standard port), prefer that.
- // This mirrors the port selection in newNoiseClient from noise.go.
- if d.HTTPPort != "" && d.HTTPPort != "80" && d.HTTPSPort == "443" {
- wsScheme = "ws"
- host = net.JoinHostPort(host, d.HTTPPort)
- }
- wsURL := &url.URL{
- Scheme: wsScheme,
- Host: host,
- Path: serverUpgradePath,
- // Can't set HTTP headers on the websocket request, so we have to to send
- // the handshake via an HTTP header.
- RawQuery: url.Values{
- controlhttpcommon.HandshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
- }.Encode(),
- }
- wsConn, _, err := websocket.Dial(ctx, wsURL.String(), &websocket.DialOptions{
- Subprotocols: []string{controlhttpcommon.UpgradeHeaderValue},
- })
- if err != nil {
- return nil, err
- }
- netConn := wsconn.NetConn(context.Background(), wsConn, websocket.MessageBinary, wsURL.String())
- cbConn, err := cont(ctx, netConn)
- if err != nil {
- netConn.Close()
- return nil, err
- }
- return &ClientConn{Conn: cbConn}, nil
- }
|