mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
c76a6e5167
Some checks are pending
checklocks / checklocks (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
Dockerfile build / deploy (push) Waiting to run
CI / race-root-integration (1/4) (push) Waiting to run
CI / race-root-integration (2/4) (push) Waiting to run
CI / race-root-integration (3/4) (push) Waiting to run
CI / race-root-integration (4/4) (push) Waiting to run
CI / test (-coverprofile=/tmp/coverage.out, amd64) (push) Waiting to run
CI / test (-race, amd64, 1/3) (push) Waiting to run
CI / test (-race, amd64, 2/3) (push) Waiting to run
CI / test (-race, amd64, 3/3) (push) Waiting to run
CI / test (386) (push) Waiting to run
CI / windows (push) Waiting to run
CI / privileged (push) Waiting to run
CI / vm (push) Waiting to run
CI / race-build (push) Waiting to run
CI / cross (386, linux) (push) Waiting to run
CI / cross (amd64, darwin) (push) Waiting to run
CI / cross (amd64, freebsd) (push) Waiting to run
CI / fuzz (push) Waiting to run
CI / cross (amd64, openbsd) (push) Waiting to run
CI / cross (amd64, windows) (push) Waiting to run
CI / cross (arm, 5, linux) (push) Waiting to run
CI / cross (arm, 7, linux) (push) Waiting to run
CI / cross (arm64, darwin) (push) Waiting to run
CI / cross (arm64, linux) (push) Waiting to run
CI / cross (arm64, windows) (push) Waiting to run
CI / cross (loong64, linux) (push) Waiting to run
CI / ios (push) Waiting to run
CI / depaware (push) Waiting to run
CI / crossmin (amd64, plan9) (push) Waiting to run
CI / crossmin (ppc64, aix) (push) Waiting to run
CI / android (push) Waiting to run
CI / wasm (push) Waiting to run
CI / tailscale_go (push) Waiting to run
CI / go_generate (push) Waiting to run
CI / go_mod_tidy (push) Waiting to run
CI / licenses (push) Waiting to run
CI / staticcheck (386, windows) (push) Waiting to run
CI / staticcheck (amd64, darwin) (push) Waiting to run
CI / staticcheck (amd64, linux) (push) Waiting to run
CI / staticcheck (amd64, windows) (push) Waiting to run
CI / notify_slack (push) Blocked by required conditions
CI / check_mergeability (push) Blocked by required conditions
In f77821fd63
(released in v1.72.0), we made the client tell a DERP server
when the connection was not its ideal choice (the first node in its region).
But we didn't do anything with that information until now. This adds a
metric about how many such connections are on a given derper, and also
adds a bit to the PeerPresentFlags bitmask so watchers can identify
(and rebalance) them.
Updates tailscale/corp#372
Change-Id: Ief8af448750aa6d598e5939a57c062f4e55962be
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
115 lines
3.3 KiB
Go
115 lines
3.3 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package derphttp
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"tailscale.com/derp"
|
|
)
|
|
|
|
// fastStartHeader is the header (with value "1") that signals to the HTTP
|
|
// server that the DERP HTTP client does not want the HTTP 101 response
|
|
// headers and it will begin writing & reading the DERP protocol immediately
|
|
// following its HTTP request.
|
|
const fastStartHeader = "Derp-Fast-Start"
|
|
|
|
// Handler returns an http.Handler to be mounted at /derp, serving s.
|
|
func Handler(s *derp.Server) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
// These are installed both here and in cmd/derper. The check here
|
|
// catches both cmd/derper run with DERP disabled (STUN only mode) as
|
|
// well as DERP being run in tests with derphttp.Handler directly,
|
|
// as netcheck still assumes this replies.
|
|
switch r.URL.Path {
|
|
case "/derp/probe", "/derp/latency-check":
|
|
ProbeHandler(w, r)
|
|
return
|
|
}
|
|
|
|
up := strings.ToLower(r.Header.Get("Upgrade"))
|
|
if up != "websocket" && up != "derp" {
|
|
if up != "" {
|
|
log.Printf("Weird upgrade: %q", up)
|
|
}
|
|
http.Error(w, "DERP requires connection upgrade", http.StatusUpgradeRequired)
|
|
return
|
|
}
|
|
|
|
fastStart := r.Header.Get(fastStartHeader) == "1"
|
|
|
|
h, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
http.Error(w, "HTTP does not support general TCP support", 500)
|
|
return
|
|
}
|
|
|
|
netConn, conn, err := h.Hijack()
|
|
if err != nil {
|
|
log.Printf("Hijack failed: %v", err)
|
|
http.Error(w, "HTTP does not support general TCP support", 500)
|
|
return
|
|
}
|
|
|
|
if !fastStart {
|
|
pubKey := s.PublicKey()
|
|
fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
|
|
"Upgrade: DERP\r\n"+
|
|
"Connection: Upgrade\r\n"+
|
|
"Derp-Version: %v\r\n"+
|
|
"Derp-Public-Key: %s\r\n\r\n",
|
|
derp.ProtocolVersion,
|
|
pubKey.UntypedHexString())
|
|
}
|
|
|
|
if v := r.Header.Get(derp.IdealNodeHeader); v != "" {
|
|
ctx = derp.IdealNodeContextKey.WithValue(ctx, v)
|
|
}
|
|
|
|
s.Accept(ctx, netConn, conn, netConn.RemoteAddr().String())
|
|
})
|
|
}
|
|
|
|
// ProbeHandler is the endpoint that clients without UDP access (including js/wasm) hit to measure
|
|
// DERP latency, as a replacement for UDP STUN queries.
|
|
func ProbeHandler(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case "HEAD", "GET":
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
default:
|
|
http.Error(w, "bogus probe method", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
// ServeNoContent generates the /generate_204 response used by Tailscale's
|
|
// captive portal detection.
|
|
func ServeNoContent(w http.ResponseWriter, r *http.Request) {
|
|
if challenge := r.Header.Get(NoContentChallengeHeader); challenge != "" {
|
|
badChar := strings.IndexFunc(challenge, func(r rune) bool {
|
|
return !isChallengeChar(r)
|
|
}) != -1
|
|
if len(challenge) <= 64 && !badChar {
|
|
w.Header().Set(NoContentResponseHeader, "response "+challenge)
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func isChallengeChar(c rune) bool {
|
|
// Semi-randomly chosen as a limited set of valid characters
|
|
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
|
|
('0' <= c && c <= '9') ||
|
|
c == '.' || c == '-' || c == '_'
|
|
}
|
|
|
|
const (
|
|
NoContentChallengeHeader = "X-Tailscale-Challenge"
|
|
NoContentResponseHeader = "X-Tailscale-Response"
|
|
)
|