feature/c2n: move answerC2N code + deps out of control/controlclient

c2n was already a conditional feature, but it didn't have a
feature/c2n directory before (rather, it was using consts + DCE). This
adds it, and moves some code, which removes the httprec dependency.

Also, remove some unnecessary code from our httprec fork.

Updates #12614

Change-Id: I2fbe538e09794c517038e35a694a363312c426a2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/17445/head
Brad Fitzpatrick 2 months ago committed by Brad Fitzpatrick
parent db65f3fcf8
commit 2e381557b8

@ -700,6 +700,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/envknob/featureknob from tailscale.com/client/web+
tailscale.com/feature from tailscale.com/ipn/ipnext+
tailscale.com/feature/buildfeatures from tailscale.com/wgengine/magicsock+
tailscale.com/feature/c2n from tailscale.com/tsnet
tailscale.com/feature/condregister/oauthkey from tailscale.com/tsnet
tailscale.com/feature/condregister/portmapper from tailscale.com/tsnet
tailscale.com/feature/condregister/useproxy from tailscale.com/tsnet
@ -791,7 +792,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/tailcfg from tailscale.com/client/local+
tailscale.com/tempfork/acme from tailscale.com/ipn/ipnlocal
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
tailscale.com/tempfork/httprec from tailscale.com/feature/c2n
tailscale.com/tka from tailscale.com/client/local+
tailscale.com/tsconst from tailscale.com/net/netmon+
tailscale.com/tsd from tailscale.com/ipn/ipnlocal+

@ -117,7 +117,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/syncs from tailscale.com/cmd/tailscaled+
tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
tailscale.com/tka from tailscale.com/control/controlclient+
tailscale.com/tsconst from tailscale.com/net/netns
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
@ -211,7 +210,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/exp/maps from tailscale.com/ipn/store/mem
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from tailscale.com/ipn/ipnlocal+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal
golang.org/x/net/icmp from tailscale.com/net/ping
golang.org/x/net/idna from golang.org/x/net/http/httpguts
golang.org/x/net/internal/iana from golang.org/x/net/icmp+

@ -142,7 +142,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/syncs from tailscale.com/cmd/tailscaled+
tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
tailscale.com/tempfork/spf13/cobra from tailscale.com/cmd/tailscale/cli/ffcomplete+
tailscale.com/tka from tailscale.com/control/controlclient+
tailscale.com/tsconst from tailscale.com/net/netns+
@ -239,7 +238,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/exp/maps from tailscale.com/ipn/store/mem
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from tailscale.com/cmd/tailscale/cli+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal
golang.org/x/net/icmp from tailscale.com/net/ping
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
golang.org/x/net/internal/iana from golang.org/x/net/icmp+

@ -275,6 +275,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/feature/ace from tailscale.com/feature/condregister
tailscale.com/feature/appconnectors from tailscale.com/feature/condregister
tailscale.com/feature/buildfeatures from tailscale.com/wgengine/magicsock+
tailscale.com/feature/c2n from tailscale.com/feature/condregister
tailscale.com/feature/capture from tailscale.com/feature/condregister
tailscale.com/feature/clientupdate from tailscale.com/feature/condregister
tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled
@ -379,7 +380,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/tempfork/acme from tailscale.com/ipn/ipnlocal
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
tailscale.com/tempfork/httprec from tailscale.com/feature/c2n
tailscale.com/tka from tailscale.com/client/local+
tailscale.com/tsconst from tailscale.com/net/netmon+
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
@ -502,7 +503,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
golang.org/x/net/icmp from tailscale.com/net/ping+
golang.org/x/net/idna from golang.org/x/net/http/httpguts+

@ -142,6 +142,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/envknob/featureknob from tailscale.com/client/web+
tailscale.com/feature from tailscale.com/ipn/ipnext+
tailscale.com/feature/buildfeatures from tailscale.com/wgengine/magicsock+
tailscale.com/feature/c2n from tailscale.com/tsnet
tailscale.com/feature/condregister/oauthkey from tailscale.com/tsnet
tailscale.com/feature/condregister/portmapper from tailscale.com/tsnet
tailscale.com/feature/condregister/useproxy from tailscale.com/tsnet
@ -218,7 +219,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/tailcfg from tailscale.com/client/local+
tailscale.com/tempfork/acme from tailscale.com/ipn/ipnlocal
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
tailscale.com/tempfork/httprec from tailscale.com/feature/c2n
tailscale.com/tka from tailscale.com/client/local+
tailscale.com/tsconst from tailscale.com/ipn/ipnlocal+
tailscale.com/tsd from tailscale.com/ipn/ipnext+
@ -334,7 +335,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
golang.org/x/net/icmp from github.com/prometheus-community/pro-bing+
golang.org/x/net/idna from golang.org/x/net/http/httpguts+

@ -4,7 +4,6 @@
package controlclient
import (
"bufio"
"bytes"
"cmp"
"context"
@ -44,7 +43,6 @@ import (
"tailscale.com/net/tlsdial"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg"
"tailscale.com/tempfork/httprec"
"tailscale.com/tka"
"tailscale.com/tstime"
"tailscale.com/types/key"
@ -1389,6 +1387,10 @@ func (c *Direct) isUniquePingRequest(pr *tailcfg.PingRequest) bool {
return true
}
// HookAnswerC2NPing is where feature/c2n conditionally registers support
// for handling C2N (control-to-node) HTTP requests.
var HookAnswerC2NPing feature.Hook[func(logger.Logf, http.Handler, *http.Client, *tailcfg.PingRequest)]
func (c *Direct) answerPing(pr *tailcfg.PingRequest) {
httpc := c.httpc
useNoise := pr.URLIsNoise || pr.Types == "c2n"
@ -1416,7 +1418,9 @@ func (c *Direct) answerPing(pr *tailcfg.PingRequest) {
c.logf("refusing to answer c2n ping without noise")
return
}
answerC2NPing(c.logf, c.c2nHandler, httpc, pr)
if f, ok := HookAnswerC2NPing.GetOk(); ok {
f(c.logf, c.c2nHandler, httpc, pr)
}
return
}
for _, t := range strings.Split(pr.Types, ",") {
@ -1451,54 +1455,6 @@ func answerHeadPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest) {
}
}
func answerC2NPing(logf logger.Logf, c2nHandler http.Handler, c *http.Client, pr *tailcfg.PingRequest) {
if c2nHandler == nil {
logf("answerC2NPing: c2nHandler not defined")
return
}
hreq, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(pr.Payload)))
if err != nil {
logf("answerC2NPing: ReadRequest: %v", err)
return
}
if pr.Log {
logf("answerC2NPing: got c2n request for %v ...", hreq.RequestURI)
}
handlerTimeout := time.Minute
if v := hreq.Header.Get("C2n-Handler-Timeout"); v != "" {
handlerTimeout, _ = time.ParseDuration(v)
}
handlerCtx, cancel := context.WithTimeout(context.Background(), handlerTimeout)
defer cancel()
hreq = hreq.WithContext(handlerCtx)
rec := httprec.NewRecorder()
c2nHandler.ServeHTTP(rec, hreq)
cancel()
c2nResBuf := new(bytes.Buffer)
rec.Result().Write(c2nResBuf)
replyCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
req, err := http.NewRequestWithContext(replyCtx, "POST", pr.URL, c2nResBuf)
if err != nil {
logf("answerC2NPing: NewRequestWithContext: %v", err)
return
}
if pr.Log {
logf("answerC2NPing: sending POST ping to %v ...", pr.URL)
}
t0 := clock.Now()
_, err = c.Do(req)
d := time.Since(t0).Round(time.Millisecond)
if err != nil {
logf("answerC2NPing error: %v to %v (after %v)", err, pr.URL, d)
} else if pr.Log {
logf("answerC2NPing complete to %v (after %v)", pr.URL, d)
}
}
// sleepAsRequest implements the sleep for a tailcfg.Debug message requesting
// that the client sleep. The complication is that while we're sleeping (if for
// a long time), we need to periodically reset the watchdog timer before it

@ -0,0 +1,70 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package c2n registers support for C2N (Control-to-Node) communications.
package c2n
import (
"bufio"
"bytes"
"context"
"net/http"
"time"
"tailscale.com/control/controlclient"
"tailscale.com/tailcfg"
"tailscale.com/tempfork/httprec"
"tailscale.com/types/logger"
)
func init() {
controlclient.HookAnswerC2NPing.Set(answerC2NPing)
}
func answerC2NPing(logf logger.Logf, c2nHandler http.Handler, c *http.Client, pr *tailcfg.PingRequest) {
if c2nHandler == nil {
logf("answerC2NPing: c2nHandler not defined")
return
}
hreq, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(pr.Payload)))
if err != nil {
logf("answerC2NPing: ReadRequest: %v", err)
return
}
if pr.Log {
logf("answerC2NPing: got c2n request for %v ...", hreq.RequestURI)
}
handlerTimeout := time.Minute
if v := hreq.Header.Get("C2n-Handler-Timeout"); v != "" {
handlerTimeout, _ = time.ParseDuration(v)
}
handlerCtx, cancel := context.WithTimeout(context.Background(), handlerTimeout)
defer cancel()
hreq = hreq.WithContext(handlerCtx)
rec := httprec.NewRecorder()
c2nHandler.ServeHTTP(rec, hreq)
cancel()
c2nResBuf := new(bytes.Buffer)
rec.Result().Write(c2nResBuf)
replyCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
req, err := http.NewRequestWithContext(replyCtx, "POST", pr.URL, c2nResBuf)
if err != nil {
logf("answerC2NPing: NewRequestWithContext: %v", err)
return
}
if pr.Log {
logf("answerC2NPing: sending POST ping to %v ...", pr.URL)
}
t0 := time.Now()
_, err = c.Do(req)
d := time.Since(t0).Round(time.Millisecond)
if err != nil {
logf("answerC2NPing error: %v to %v (after %v)", err, pr.URL, d)
} else if pr.Log {
logf("answerC2NPing complete to %v (after %v)", pr.URL, d)
}
}

@ -0,0 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_c2n
package condregister
import _ "tailscale.com/feature/c2n"

@ -14,9 +14,6 @@ import (
"net/http"
"net/textproto"
"strconv"
"strings"
"golang.org/x/net/http/httpguts"
)
// ResponseRecorder is an implementation of [http.ResponseWriter] that
@ -59,10 +56,6 @@ func NewRecorder() *ResponseRecorder {
}
}
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
// an explicit DefaultRemoteAddr isn't set on [ResponseRecorder].
const DefaultRemoteAddr = "1.2.3.4"
// Header implements [http.ResponseWriter]. It returns the response
// headers to mutate within a handler. To test the headers that were
// written after a handler completes, use the [ResponseRecorder.Result] method and see
@ -206,37 +199,6 @@ func (rw *ResponseRecorder) Result() *http.Response {
res.Body = http.NoBody
}
res.ContentLength = parseContentLength(res.Header.Get("Content-Length"))
if trailers, ok := rw.snapHeader["Trailer"]; ok {
res.Trailer = make(http.Header, len(trailers))
for _, k := range trailers {
for _, k := range strings.Split(k, ",") {
k = http.CanonicalHeaderKey(textproto.TrimString(k))
if !httpguts.ValidTrailerHeader(k) {
// Ignore since forbidden by RFC 7230, section 4.1.2.
continue
}
vv, ok := rw.HeaderMap[k]
if !ok {
continue
}
vv2 := make([]string, len(vv))
copy(vv2, vv)
res.Trailer[k] = vv2
}
}
}
for k, vv := range rw.HeaderMap {
if !strings.HasPrefix(k, http.TrailerPrefix) {
continue
}
if res.Trailer == nil {
res.Trailer = make(http.Header)
}
for _, v := range vv {
res.Trailer.Add(strings.TrimPrefix(k, http.TrailerPrefix), v)
}
}
return res
}

@ -138,6 +138,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/envknob/featureknob from tailscale.com/client/web+
tailscale.com/feature from tailscale.com/ipn/ipnext+
tailscale.com/feature/buildfeatures from tailscale.com/wgengine/magicsock+
tailscale.com/feature/c2n from tailscale.com/tsnet
tailscale.com/feature/condregister/oauthkey from tailscale.com/tsnet
tailscale.com/feature/condregister/portmapper from tailscale.com/tsnet
tailscale.com/feature/condregister/useproxy from tailscale.com/tsnet
@ -214,7 +215,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/tailcfg from tailscale.com/client/local+
tailscale.com/tempfork/acme from tailscale.com/ipn/ipnlocal
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
tailscale.com/tempfork/httprec from tailscale.com/feature/c2n
tailscale.com/tka from tailscale.com/client/local+
tailscale.com/tsconst from tailscale.com/ipn/ipnlocal+
tailscale.com/tsd from tailscale.com/ipn/ipnext+
@ -327,7 +328,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
golang.org/x/net/icmp from github.com/prometheus-community/pro-bing+
golang.org/x/net/idna from golang.org/x/net/http/httpguts+

@ -29,6 +29,7 @@ import (
"tailscale.com/client/local"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
_ "tailscale.com/feature/c2n"
_ "tailscale.com/feature/condregister/oauthkey"
_ "tailscale.com/feature/condregister/portmapper"
_ "tailscale.com/feature/condregister/useproxy"

Loading…
Cancel
Save