summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2024-08-06 17:33:38 -0700
committerBrad Fitzpatrick <brad@danga.com>2024-08-06 17:53:33 -0700
commit6ca078c46ecda87fd8cb5f083f5b494ce06f3961 (patch)
tree7b57fe42c8e7e770023b81574d427f13768872ab
parenta93dc6cdb10bcc87742011d3746660f06cf8cd1e (diff)
downloadtailscale-6ca078c46ecda87fd8cb5f083f5b494ce06f3961.tar.xz
tailscale-6ca078c46ecda87fd8cb5f083f5b494ce06f3961.zip
cmd/derper: move 204 handler from package main to derphttp
Updates #13038 Change-Id: I28a8284dbe49371cae0e9098205c7c5f17225b40 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--cmd/derper/derper.go29
-rw-r--r--cmd/derper/derper_test.go9
-rw-r--r--derp/derphttp/derphttp_server.go27
3 files changed, 34 insertions, 31 deletions
diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go
index 76151175c..80c9dc44f 100644
--- a/cmd/derper/derper.go
+++ b/cmd/derper/derper.go
@@ -237,7 +237,7 @@ func main() {
tsweb.AddBrowserHeaders(w)
io.WriteString(w, "User-agent: *\nDisallow: /\n")
}))
- mux.Handle("/generate_204", http.HandlerFunc(serveNoContent))
+ mux.Handle("/generate_204", http.HandlerFunc(derphttp.ServeNoContent))
debug := tsweb.Debugger(mux)
debug.KV("TLS hostname", *hostname)
debug.KV("Mesh key", s.HasMeshKey())
@@ -337,7 +337,7 @@ func main() {
if *httpPort > -1 {
go func() {
port80mux := http.NewServeMux()
- port80mux.HandleFunc("/generate_204", serveNoContent)
+ port80mux.HandleFunc("/generate_204", derphttp.ServeNoContent)
port80mux.Handle("/", certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}))
port80srv := &http.Server{
Addr: net.JoinHostPort(listenHost, fmt.Sprintf("%d", *httpPort)),
@@ -378,31 +378,6 @@ func main() {
}
}
-const (
- noContentChallengeHeader = "X-Tailscale-Challenge"
- noContentResponseHeader = "X-Tailscale-Response"
-)
-
-// For 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 == '_'
-}
-
var validProdHostname = regexp.MustCompile(`^derp([^.]*)\.tailscale\.com\.?$`)
func prodAutocertHostPolicy(_ context.Context, host string) error {
diff --git a/cmd/derper/derper_test.go b/cmd/derper/derper_test.go
index 1af7c3abe..553a78f9f 100644
--- a/cmd/derper/derper_test.go
+++ b/cmd/derper/derper_test.go
@@ -10,6 +10,7 @@ import (
"strings"
"testing"
+ "tailscale.com/derp/derphttp"
"tailscale.com/tstest/deptest"
)
@@ -76,20 +77,20 @@ func TestNoContent(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "https://localhost/generate_204", nil)
if tt.input != "" {
- req.Header.Set(noContentChallengeHeader, tt.input)
+ req.Header.Set(derphttp.NoContentChallengeHeader, tt.input)
}
w := httptest.NewRecorder()
- serveNoContent(w, req)
+ derphttp.ServeNoContent(w, req)
resp := w.Result()
if tt.want == "" {
- if h, found := resp.Header[noContentResponseHeader]; found {
+ if h, found := resp.Header[derphttp.NoContentResponseHeader]; found {
t.Errorf("got %+v; expected no response header", h)
}
return
}
- if got := resp.Header.Get(noContentResponseHeader); got != tt.want {
+ if got := resp.Header.Get(derphttp.NoContentResponseHeader); got != tt.want {
t.Errorf("got %q; want %q", got, tt.want)
}
})
diff --git a/derp/derphttp/derphttp_server.go b/derp/derphttp/derphttp_server.go
index d1193f383..41ce86764 100644
--- a/derp/derphttp/derphttp_server.go
+++ b/derp/derphttp/derphttp_server.go
@@ -18,6 +18,7 @@ import (
// 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) {
// These are installed both here and in cmd/derper. The check here
@@ -79,3 +80,29 @@ func ProbeHandler(w http.ResponseWriter, r *http.Request) {
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"
+)