mirror of
https://github.com/tailscale/tailscale.git
synced 2026-02-08 06:51:42 -05:00
This file was never truly necessary and has never actually been used in the history of Tailscale's open source releases. A Brief History of AUTHORS files --- The AUTHORS file was a pattern developed at Google, originally for Chromium, then adopted by Go and a bunch of other projects. The problem was that Chromium originally had a copyright line only recognizing Google as the copyright holder. Because Google (and most open source projects) do not require copyright assignemnt for contributions, each contributor maintains their copyright. Some large corporate contributors then tried to add their own name to the copyright line in the LICENSE file or in file headers. This quickly becomes unwieldy, and puts a tremendous burden on anyone building on top of Chromium, since the license requires that they keep all copyright lines intact. The compromise was to create an AUTHORS file that would list all of the copyright holders. The LICENSE file and source file headers would then include that list by reference, listing the copyright holder as "The Chromium Authors". This also become cumbersome to simply keep the file up to date with a high rate of new contributors. Plus it's not always obvious who the copyright holder is. Sometimes it is the individual making the contribution, but many times it may be their employer. There is no way for the proejct maintainer to know. Eventually, Google changed their policy to no longer recommend trying to keep the AUTHORS file up to date proactively, and instead to only add to it when requested: https://opensource.google/docs/releasing/authors. They are also clear that: > Adding contributors to the AUTHORS file is entirely within the > project's discretion and has no implications for copyright ownership. It was primarily added to appease a small number of large contributors that insisted that they be recognized as copyright holders (which was entirely their right to do). But it's not truly necessary, and not even the most accurate way of identifying contributors and/or copyright holders. In practice, we've never added anyone to our AUTHORS file. It only lists Tailscale, so it's not really serving any purpose. It also causes confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header in other open source repos which don't actually have an AUTHORS file, so it's ambiguous what that means. Instead, we just acknowledge that the contributors to Tailscale (whoever they are) are copyright holders for their individual contributions. We also have the benefit of using the DCO (developercertificate.org) which provides some additional certification of their right to make the contribution. The source file changes were purely mechanical with: git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g' Updates #cleanup Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
220 lines
6.8 KiB
Go
220 lines
6.8 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build !ios
|
|
|
|
// Package controlhttpserver contains the HTTP server side of the ts2021 control protocol.
|
|
package controlhttpserver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/coder/websocket"
|
|
"tailscale.com/control/controlbase"
|
|
"tailscale.com/control/controlhttp/controlhttpcommon"
|
|
"tailscale.com/net/netutil"
|
|
"tailscale.com/net/wsconn"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
// AcceptHTTP upgrades the HTTP request given by w and r into a Tailscale
|
|
// control protocol base transport connection.
|
|
//
|
|
// AcceptHTTP always writes an HTTP response to w. The caller must not attempt
|
|
// their own response after calling AcceptHTTP.
|
|
//
|
|
// earlyWrite optionally specifies a func to write to the noise connection
|
|
// (encrypted). It receives the negotiated version and a writer to write to, if
|
|
// desired.
|
|
func AcceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, private key.MachinePrivate, earlyWrite func(protocolVersion int, w io.Writer) error) (*controlbase.Conn, error) {
|
|
return acceptHTTP(ctx, w, r, private, earlyWrite)
|
|
}
|
|
|
|
func acceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, private key.MachinePrivate, earlyWrite func(protocolVersion int, w io.Writer) error) (_ *controlbase.Conn, retErr error) {
|
|
next := strings.ToLower(r.Header.Get("Upgrade"))
|
|
if next == "" {
|
|
http.Error(w, "missing next protocol", http.StatusBadRequest)
|
|
return nil, errors.New("no next protocol in HTTP request")
|
|
}
|
|
if next == "websocket" {
|
|
return acceptWebsocket(ctx, w, r, private)
|
|
}
|
|
if next != controlhttpcommon.UpgradeHeaderValue {
|
|
http.Error(w, "unknown next protocol", http.StatusBadRequest)
|
|
return nil, fmt.Errorf("client requested unhandled next protocol %q", next)
|
|
}
|
|
|
|
initB64 := r.Header.Get(controlhttpcommon.HandshakeHeaderName)
|
|
if initB64 == "" {
|
|
http.Error(w, "missing Tailscale handshake header", http.StatusBadRequest)
|
|
return nil, errors.New("no tailscale handshake header in HTTP request")
|
|
}
|
|
init, err := base64.StdEncoding.DecodeString(initB64)
|
|
if err != nil {
|
|
http.Error(w, "invalid tailscale handshake header", http.StatusBadRequest)
|
|
return nil, fmt.Errorf("decoding base64 handshake header: %v", err)
|
|
}
|
|
|
|
hijacker, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
http.Error(w, "make request over HTTP/1", http.StatusBadRequest)
|
|
return nil, errors.New("can't hijack client connection")
|
|
}
|
|
|
|
w.Header().Set("Upgrade", controlhttpcommon.UpgradeHeaderValue)
|
|
w.Header().Set("Connection", "upgrade")
|
|
w.WriteHeader(http.StatusSwitchingProtocols)
|
|
|
|
conn, brw, err := hijacker.Hijack()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("hijacking client connection: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
conn.Close()
|
|
}
|
|
}()
|
|
|
|
if err := brw.Flush(); err != nil {
|
|
return nil, fmt.Errorf("flushing hijacked HTTP buffer: %w", err)
|
|
}
|
|
conn = netutil.NewDrainBufConn(conn, brw.Reader)
|
|
|
|
cwc := newWriteCorkingConn(conn)
|
|
|
|
nc, err := controlbase.Server(ctx, cwc, private, init)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("noise handshake failed: %w", err)
|
|
}
|
|
|
|
if earlyWrite != nil {
|
|
if deadline, ok := ctx.Deadline(); ok {
|
|
if err := conn.SetDeadline(deadline); err != nil {
|
|
return nil, fmt.Errorf("setting conn deadline: %w", err)
|
|
}
|
|
defer conn.SetDeadline(time.Time{})
|
|
}
|
|
if err := earlyWrite(nc.ProtocolVersion(), nc); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := cwc.uncork(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nc, nil
|
|
}
|
|
|
|
// acceptWebsocket upgrades a WebSocket connection (from a client that cannot
|
|
// speak HTTP) to a Tailscale control protocol base transport connection.
|
|
func acceptWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request, private key.MachinePrivate) (*controlbase.Conn, error) {
|
|
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
|
Subprotocols: []string{controlhttpcommon.UpgradeHeaderValue},
|
|
OriginPatterns: []string{"*"},
|
|
// Disable compression because we transmit Noise messages that are not
|
|
// compressible.
|
|
// Additionally, Safari has a broken implementation of compression
|
|
// (see https://github.com/nhooyr/websocket/issues/218) that makes
|
|
// enabling it actively harmful.
|
|
CompressionMode: websocket.CompressionDisabled,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not accept WebSocket connection %v", err)
|
|
}
|
|
if c.Subprotocol() != controlhttpcommon.UpgradeHeaderValue {
|
|
c.Close(websocket.StatusPolicyViolation, "client must speak the control subprotocol")
|
|
return nil, fmt.Errorf("Unexpected subprotocol %q", c.Subprotocol())
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
c.Close(websocket.StatusPolicyViolation, "Could not parse parameters")
|
|
return nil, fmt.Errorf("parse query parameters: %v", err)
|
|
}
|
|
initB64 := r.Form.Get(controlhttpcommon.HandshakeHeaderName)
|
|
if initB64 == "" {
|
|
c.Close(websocket.StatusPolicyViolation, "missing Tailscale handshake parameter")
|
|
return nil, errors.New("no tailscale handshake parameter in HTTP request")
|
|
}
|
|
init, err := base64.StdEncoding.DecodeString(initB64)
|
|
if err != nil {
|
|
c.Close(websocket.StatusPolicyViolation, "invalid tailscale handshake parameter")
|
|
return nil, fmt.Errorf("decoding base64 handshake parameter: %v", err)
|
|
}
|
|
|
|
conn := wsconn.NetConn(ctx, c, websocket.MessageBinary, r.RemoteAddr)
|
|
nc, err := controlbase.Server(ctx, conn, private, init)
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("noise handshake failed: %w", err)
|
|
}
|
|
|
|
return nc, nil
|
|
}
|
|
|
|
// corkConn is a net.Conn wrapper that initially buffers all writes until uncork
|
|
// is called. If the conn is corked and a Read occurs, the Read will flush any
|
|
// buffered (corked) write.
|
|
//
|
|
// Until uncorked, Read/Write/uncork may be not called concurrently.
|
|
//
|
|
// Deadlines still work, but a corked write ignores deadlines until a Read or
|
|
// uncork goes to do that Write.
|
|
//
|
|
// Use newWriteCorkingConn to create one.
|
|
type corkConn struct {
|
|
net.Conn
|
|
corked bool
|
|
buf []byte // corked data
|
|
}
|
|
|
|
func newWriteCorkingConn(c net.Conn) *corkConn {
|
|
return &corkConn{Conn: c, corked: true}
|
|
}
|
|
|
|
func (c *corkConn) Write(b []byte) (int, error) {
|
|
if c.corked {
|
|
c.buf = append(c.buf, b...)
|
|
return len(b), nil
|
|
}
|
|
return c.Conn.Write(b)
|
|
}
|
|
|
|
func (c *corkConn) Read(b []byte) (int, error) {
|
|
if c.corked {
|
|
if err := c.flush(); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return c.Conn.Read(b)
|
|
}
|
|
|
|
// uncork flushes any buffered data and uncorks the connection so future Writes
|
|
// don't buffer. It may not be called concurrently with reads or writes and
|
|
// may only be called once.
|
|
func (c *corkConn) uncork() error {
|
|
if !c.corked {
|
|
panic("usage error; uncork called twice") // worth panicking to catch misuse
|
|
}
|
|
err := c.flush()
|
|
c.corked = false
|
|
return err
|
|
}
|
|
|
|
func (c *corkConn) flush() error {
|
|
if len(c.buf) == 0 {
|
|
return nil
|
|
}
|
|
_, err := c.Conn.Write(c.buf)
|
|
c.buf = nil
|
|
return err
|
|
}
|