summaryrefslogtreecommitdiffhomepage
path: root/feature/c2n/c2n.go
blob: 331a9af955a07d39530bf12514251abe4ed5aecc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// Copyright (c) Tailscale Inc & contributors
// 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)
	}
}