handler.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package derpserver
  4. import (
  5. "fmt"
  6. "log"
  7. "net/http"
  8. "strings"
  9. "tailscale.com/derp"
  10. )
  11. // Handler returns an http.Handler to be mounted at /derp, serving s.
  12. func Handler(s *Server) http.Handler {
  13. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  14. ctx := r.Context()
  15. // These are installed both here and in cmd/derper. The check here
  16. // catches both cmd/derper run with DERP disabled (STUN only mode) as
  17. // well as DERP being run in tests with derphttp.Handler directly,
  18. // as netcheck still assumes this replies.
  19. switch r.URL.Path {
  20. case "/derp/probe", "/derp/latency-check":
  21. ProbeHandler(w, r)
  22. return
  23. }
  24. up := strings.ToLower(r.Header.Get("Upgrade"))
  25. if up != "websocket" && up != "derp" {
  26. if up != "" {
  27. log.Printf("Weird upgrade: %q", up)
  28. }
  29. http.Error(w, "DERP requires connection upgrade", http.StatusUpgradeRequired)
  30. return
  31. }
  32. fastStart := r.Header.Get(derp.FastStartHeader) == "1"
  33. h, ok := w.(http.Hijacker)
  34. if !ok {
  35. http.Error(w, "HTTP does not support general TCP support", 500)
  36. return
  37. }
  38. netConn, conn, err := h.Hijack()
  39. if err != nil {
  40. log.Printf("Hijack failed: %v", err)
  41. http.Error(w, "HTTP does not support general TCP support", 500)
  42. return
  43. }
  44. if !fastStart {
  45. pubKey := s.PublicKey()
  46. fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
  47. "Upgrade: DERP\r\n"+
  48. "Connection: Upgrade\r\n"+
  49. "Derp-Version: %v\r\n"+
  50. "Derp-Public-Key: %s\r\n\r\n",
  51. derp.ProtocolVersion,
  52. pubKey.UntypedHexString())
  53. }
  54. if v := r.Header.Get(derp.IdealNodeHeader); v != "" {
  55. ctx = IdealNodeContextKey.WithValue(ctx, v)
  56. }
  57. s.Accept(ctx, netConn, conn, netConn.RemoteAddr().String())
  58. })
  59. }
  60. // ProbeHandler is the endpoint that clients without UDP access (including js/wasm) hit to measure
  61. // DERP latency, as a replacement for UDP STUN queries.
  62. func ProbeHandler(w http.ResponseWriter, r *http.Request) {
  63. switch r.Method {
  64. case "HEAD", "GET":
  65. w.Header().Set("Access-Control-Allow-Origin", "*")
  66. default:
  67. http.Error(w, "bogus probe method", http.StatusMethodNotAllowed)
  68. }
  69. }
  70. // ServeNoContent generates the /generate_204 response used by Tailscale's
  71. // captive portal detection.
  72. func ServeNoContent(w http.ResponseWriter, r *http.Request) {
  73. if challenge := r.Header.Get(NoContentChallengeHeader); challenge != "" {
  74. badChar := strings.IndexFunc(challenge, func(r rune) bool {
  75. return !isChallengeChar(r)
  76. }) != -1
  77. if len(challenge) <= 64 && !badChar {
  78. w.Header().Set(NoContentResponseHeader, "response "+challenge)
  79. }
  80. }
  81. w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate, no-transform, max-age=0")
  82. w.WriteHeader(http.StatusNoContent)
  83. }
  84. func isChallengeChar(c rune) bool {
  85. // Semi-randomly chosen as a limited set of valid characters
  86. return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
  87. ('0' <= c && c <= '9') ||
  88. c == '.' || c == '-' || c == '_' || c == ':'
  89. }
  90. const (
  91. NoContentChallengeHeader = "X-Tailscale-Challenge"
  92. NoContentResponseHeader = "X-Tailscale-Response"
  93. )