3ec5be3f51
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>
286 lines
9.2 KiB
Go
286 lines
9.2 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build !plan9
|
|
|
|
package spdy
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
SYN_STREAM ControlFrameType = 1 // https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt section 2.6.1
|
|
SYN_REPLY ControlFrameType = 2 // https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt section 2.6.2
|
|
SYN_PING ControlFrameType = 6 // https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt section 2.6.5
|
|
)
|
|
|
|
// spdyFrame is a parsed SPDY frame as defined in
|
|
// https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt
|
|
// A SPDY frame can be either a control frame or a data frame.
|
|
type spdyFrame struct {
|
|
Raw []byte // full frame as raw bytes
|
|
|
|
// Common frame fields:
|
|
Ctrl bool // true if this is a SPDY control frame
|
|
Payload []byte // payload as raw bytes
|
|
|
|
// Control frame fields:
|
|
Version uint16 // SPDY protocol version
|
|
Type ControlFrameType
|
|
|
|
// Data frame fields:
|
|
// StreamID is the id of the steam to which this data frame belongs.
|
|
// SPDY allows transmitting multiple data streams concurrently.
|
|
StreamID uint32
|
|
}
|
|
|
|
// Type of an SPDY control frame.
|
|
type ControlFrameType uint16
|
|
|
|
// Parse parses bytes into spdyFrame.
|
|
// If the bytes don't contain a full frame, return false.
|
|
//
|
|
// Control frame structure:
|
|
//
|
|
// +----------------------------------+
|
|
// |C| Version(15bits) | Type(16bits) |
|
|
// +----------------------------------+
|
|
// | Flags (8) | Length (24 bits) |
|
|
// +----------------------------------+
|
|
// | Data |
|
|
// +----------------------------------+
|
|
//
|
|
// Data frame structure:
|
|
//
|
|
// +----------------------------------+
|
|
// |C| Stream-ID (31bits) |
|
|
// +----------------------------------+
|
|
// | Flags (8) | Length (24 bits) |
|
|
// +----------------------------------+
|
|
// | Data |
|
|
// +----------------------------------+
|
|
//
|
|
// https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt
|
|
func (sf *spdyFrame) Parse(b []byte, log *zap.SugaredLogger) (ok bool, _ error) {
|
|
const (
|
|
spdyHeaderLength = 8
|
|
)
|
|
have := len(b)
|
|
if have < spdyHeaderLength { // input does not contain full frame
|
|
return false, nil
|
|
}
|
|
|
|
if !isSPDYFrameHeader(b) {
|
|
return false, fmt.Errorf("bytes %v do not seem to contain SPDY frames. Ensure that you are using a SPDY based client to 'kubectl exec'.", b)
|
|
}
|
|
|
|
payloadLength := readInt24(b[5:8])
|
|
frameLength := payloadLength + spdyHeaderLength
|
|
if have < frameLength { // input does not contain full frame
|
|
return false, nil
|
|
}
|
|
|
|
frame := b[:frameLength:frameLength] // enforce frameLength capacity
|
|
|
|
sf.Raw = frame
|
|
sf.Payload = frame[spdyHeaderLength:frameLength]
|
|
|
|
sf.Ctrl = hasControlBitSet(frame)
|
|
|
|
if !sf.Ctrl { // data frame
|
|
sf.StreamID = dataFrameStreamID(frame)
|
|
return true, nil
|
|
}
|
|
|
|
sf.Version = controlFrameVersion(frame)
|
|
sf.Type = controlFrameType(frame)
|
|
return true, nil
|
|
}
|
|
|
|
// parseHeaders retrieves any headers from this spdyFrame.
|
|
func (sf *spdyFrame) parseHeaders(z *zlibReader, log *zap.SugaredLogger) (http.Header, error) {
|
|
if !sf.Ctrl {
|
|
return nil, fmt.Errorf("[unexpected] parseHeaders called for a frame that is not a control frame")
|
|
}
|
|
const (
|
|
// +------------------------------------+
|
|
// |X| Stream-ID (31bits) |
|
|
// +------------------------------------+
|
|
// |X| Associated-To-Stream-ID (31bits) |
|
|
// +------------------------------------+
|
|
// | Pri|Unused | Slot | |
|
|
// +-------------------+ |
|
|
synStreamPayloadLengthBeforeHeaders = 10
|
|
|
|
// +------------------------------------+
|
|
// |X| Stream-ID (31bits) |
|
|
//+------------------------------------+
|
|
synReplyPayloadLengthBeforeHeaders = 4
|
|
|
|
// +----------------------------------|
|
|
// | 32-bit ID |
|
|
// +----------------------------------+
|
|
pingPayloadLength = 4
|
|
)
|
|
|
|
switch sf.Type {
|
|
case SYN_STREAM:
|
|
if len(sf.Payload) < synStreamPayloadLengthBeforeHeaders {
|
|
return nil, fmt.Errorf("SYN_STREAM frame too short: %v", len(sf.Payload))
|
|
}
|
|
z.Set(sf.Payload[synStreamPayloadLengthBeforeHeaders:])
|
|
return parseHeaders(z, log)
|
|
case SYN_REPLY:
|
|
if len(sf.Payload) < synReplyPayloadLengthBeforeHeaders {
|
|
return nil, fmt.Errorf("SYN_REPLY frame too short: %v", len(sf.Payload))
|
|
}
|
|
if len(sf.Payload) == synReplyPayloadLengthBeforeHeaders {
|
|
return nil, nil // no headers
|
|
}
|
|
z.Set(sf.Payload[synReplyPayloadLengthBeforeHeaders:])
|
|
return parseHeaders(z, log)
|
|
case SYN_PING:
|
|
if len(sf.Payload) != pingPayloadLength {
|
|
return nil, fmt.Errorf("PING frame with unexpected length %v", len(sf.Payload))
|
|
}
|
|
return nil, nil // ping frame has no headers
|
|
|
|
default:
|
|
log.Infof("[unexpected] unknown control frame type %v", sf.Type)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// parseHeaders expects to be passed a reader that contains a compressed SPDY control
|
|
// frame Name/Value Header Block with 0 or more headers:
|
|
//
|
|
// | Number of Name/Value pairs (int32) | <+
|
|
// +------------------------------------+ |
|
|
// | Length of name (int32) | | This section is the "Name/Value
|
|
// +------------------------------------+ | Header Block", and is compressed.
|
|
// | Name (string) | |
|
|
// +------------------------------------+ |
|
|
// | Length of value (int32) | |
|
|
// +------------------------------------+ |
|
|
// | Value (string) | |
|
|
// +------------------------------------+ |
|
|
// | (repeats) | <+
|
|
//
|
|
// It extracts the headers and returns them as http.Header. By doing that it
|
|
// also advances the provided reader past the headers block.
|
|
// See also https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt section 2.6.10
|
|
func parseHeaders(decompressor io.Reader, log *zap.SugaredLogger) (http.Header, error) {
|
|
buf := bufPool.Get().(*bytes.Buffer)
|
|
defer bufPool.Put(buf)
|
|
buf.Reset()
|
|
|
|
// readUint32 reads the next 4 decompressed bytes from the decompressor
|
|
// as a uint32.
|
|
readUint32 := func() (uint32, error) {
|
|
const uint32Length = 4
|
|
if _, err := io.CopyN(buf, decompressor, uint32Length); err != nil { // decompress
|
|
return 0, fmt.Errorf("error decompressing bytes: %w", err)
|
|
}
|
|
return binary.BigEndian.Uint32(buf.Next(uint32Length)), nil // return as uint32
|
|
}
|
|
|
|
// readLenBytes decompresses and returns as bytes the next 'Name' or 'Value'
|
|
// field from SPDY Name/Value header block. decompressor must be at
|
|
// 'Length of name'/'Length of value' field.
|
|
readLenBytes := func() ([]byte, error) {
|
|
xLen, err := readUint32() // length of field to read
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := io.CopyN(buf, decompressor, int64(xLen)); err != nil { // decompress
|
|
return nil, err
|
|
}
|
|
return buf.Next(int(xLen)), nil
|
|
}
|
|
|
|
numHeaders, err := readUint32()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error determining num headers: %v", err)
|
|
}
|
|
h := make(http.Header, numHeaders)
|
|
for i := uint32(0); i < numHeaders; i++ {
|
|
name, err := readLenBytes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ns := string(name)
|
|
if _, ok := h[ns]; ok {
|
|
return nil, fmt.Errorf("invalid data: duplicate header %q", ns)
|
|
}
|
|
val, err := readLenBytes()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading header data: %w", err)
|
|
}
|
|
for _, v := range bytes.Split(val, headerSep) {
|
|
h.Add(ns, string(v))
|
|
}
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
// isSPDYFrame validates that the input bytes start with a valid SPDY frame
|
|
// header.
|
|
func isSPDYFrameHeader(f []byte) bool {
|
|
if hasControlBitSet(f) {
|
|
// If this is a control frame, version and type must be set.
|
|
return controlFrameVersion(f) != uint16(0) && uint16(controlFrameType(f)) != uint16(0)
|
|
}
|
|
// If this is a data frame, stream ID must be set.
|
|
return dataFrameStreamID(f) != uint32(0)
|
|
}
|
|
|
|
// spdyDataFrameStreamID returns stream ID for an SPDY data frame passed as the
|
|
// input data slice. StreaID is contained within bits [0-31) of a data frame
|
|
// header.
|
|
func dataFrameStreamID(frame []byte) uint32 {
|
|
return binary.BigEndian.Uint32(frame[0:4]) & 0x7f
|
|
}
|
|
|
|
// controlFrameType returns the type of a SPDY control frame.
|
|
// See https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt section 2.6
|
|
func controlFrameType(f []byte) ControlFrameType {
|
|
return ControlFrameType(binary.BigEndian.Uint16(f[2:4]))
|
|
}
|
|
|
|
// spdyControlFrameVersion returns SPDY version extracted from input bytes that
|
|
// must be a SPDY control frame.
|
|
func controlFrameVersion(frame []byte) uint16 {
|
|
bs := binary.BigEndian.Uint16(frame[0:2]) // first 16 bits
|
|
return bs & 0x7f // discard control bit
|
|
}
|
|
|
|
// hasControlBitSet returns true if the passsed bytes have SPDY control bit set.
|
|
// SPDY frames can be either control frames or data frames. A control frame has
|
|
// control bit set to 1 and a data frame has it set to 0.
|
|
func hasControlBitSet(frame []byte) bool {
|
|
return frame[0]&0x80 == 128 // 0x80
|
|
}
|
|
|
|
var bufPool = sync.Pool{
|
|
New: func() any {
|
|
return new(bytes.Buffer)
|
|
},
|
|
}
|
|
|
|
// Headers in SPDY header name/value block are separated by a 0 byte.
|
|
// https://www.ietf.org/archive/id/draft-mbelshe-httpbis-spdy-00.txt section 2.6.10
|
|
var headerSep = []byte{0}
|
|
|
|
func readInt24(b []byte) int {
|
|
_ = b[2] // bounds check hint to compiler; see golang.org/issue/14808
|
|
return int(b[0])<<16 | int(b[1])<<8 | int(b[2])
|
|
}
|