headscale/derp_server.go

257 lines
6.7 KiB
Go
Raw Normal View History

package headscale
import (
"context"
2022-06-18 17:51:37 +00:00
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/rs/zerolog/log"
"tailscale.com/derp"
"tailscale.com/net/stun"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
// 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"
2022-03-05 18:30:30 +00:00
type DERPServer struct {
2022-03-04 10:31:41 +00:00
tailscaleDERP *derp.Server
region tailcfg.DERPRegion
}
2022-03-05 18:30:30 +00:00
func (h *Headscale) NewDERPServer() (*DERPServer, error) {
2022-06-20 10:32:13 +00:00
log.Trace().Caller().Msg("Creating new embedded DERP server")
2022-03-06 16:35:54 +00:00
server := derp.NewServer(key.NodePrivate(*h.privateKey), log.Info().Msgf)
region, err := h.generateRegionLocalDERP()
if err != nil {
return nil, err
}
2022-03-06 16:35:54 +00:00
return &DERPServer{server, region}, nil
}
func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) {
serverURL, err := url.Parse(h.cfg.ServerURL)
if err != nil {
return tailcfg.DERPRegion{}, err
}
var host string
var port int
host, portStr, err := net.SplitHostPort(serverURL.Host)
if err != nil {
if serverURL.Scheme == "https" {
host = serverURL.Host
port = 443
} else {
host = serverURL.Host
port = 80
}
} else {
port, err = strconv.Atoi(portStr)
if err != nil {
return tailcfg.DERPRegion{}, err
}
}
localDERPregion := tailcfg.DERPRegion{
2022-03-06 16:25:21 +00:00
RegionID: h.cfg.DERP.ServerRegionID,
RegionCode: h.cfg.DERP.ServerRegionCode,
RegionName: h.cfg.DERP.ServerRegionName,
Avoid: false,
Nodes: []*tailcfg.DERPNode{
{
2022-03-06 16:25:21 +00:00
Name: fmt.Sprintf("%d", h.cfg.DERP.ServerRegionID),
RegionID: h.cfg.DERP.ServerRegionID,
HostName: host,
DERPPort: port,
},
},
}
2022-03-06 16:00:56 +00:00
_, portSTUNStr, err := net.SplitHostPort(h.cfg.DERP.STUNAddr)
if err != nil {
return tailcfg.DERPRegion{}, err
}
portSTUN, err := strconv.Atoi(portSTUNStr)
if err != nil {
return tailcfg.DERPRegion{}, err
2022-03-06 16:00:56 +00:00
}
localDERPregion.Nodes[0].STUNPort = portSTUN
2022-03-06 16:00:56 +00:00
2022-06-20 10:32:13 +00:00
log.Info().Caller().Msgf("DERP region: %+v", localDERPregion)
return localDERPregion, nil
}
2022-06-18 16:41:42 +00:00
func (h *Headscale) DERPHandler(
w http.ResponseWriter,
r *http.Request,
) {
2022-06-18 17:51:37 +00:00
log.Trace().Caller().Msgf("/derp request from %v", r.RemoteAddr)
up := strings.ToLower(r.Header.Get("Upgrade"))
if up != "websocket" && up != "derp" {
if up != "" {
log.Warn().Caller().Msgf("Weird websockets connection upgrade: %q", up)
}
2022-06-18 17:51:37 +00:00
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusUpgradeRequired)
w.Write([]byte("DERP requires connection upgrade"))
2022-03-06 16:35:54 +00:00
return
}
2022-06-18 17:51:37 +00:00
fastStart := r.Header.Get(fastStartHeader) == "1"
2022-06-18 17:51:37 +00:00
hijacker, ok := w.(http.Hijacker)
if !ok {
log.Error().Caller().Msg("DERP requires Hijacker interface from Gin")
2022-06-18 17:51:37 +00:00
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("HTTP does not support general TCP support"))
2022-03-06 16:25:21 +00:00
return
}
netConn, conn, err := hijacker.Hijack()
if err != nil {
log.Error().Caller().Err(err).Msgf("Hijack failed")
2022-06-18 17:51:37 +00:00
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("HTTP does not support general TCP support"))
2022-03-06 16:25:21 +00:00
return
}
2022-06-20 10:32:13 +00:00
log.Trace().Caller().Msgf("Hijacked connection from %v", r.RemoteAddr)
if !fastStart {
pubKey := h.privateKey.Public()
2022-05-16 12:59:46 +00:00
pubKeyStr := pubKey.UntypedHexString() // nolint
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,
2022-03-06 16:35:54 +00:00
pubKeyStr)
}
2022-03-05 18:30:30 +00:00
h.DERPServer.tailscaleDERP.Accept(netConn, conn, netConn.RemoteAddr().String())
}
2022-03-05 18:30:30 +00:00
// DERPProbeHandler is the endpoint that js/wasm clients hit to measure
// DERP latency, since they can't do UDP STUN queries.
2022-06-18 16:41:42 +00:00
func (h *Headscale) DERPProbeHandler(
w http.ResponseWriter,
r *http.Request,
) {
2022-06-18 17:51:37 +00:00
switch r.Method {
case "HEAD", "GET":
2022-06-18 17:51:37 +00:00
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusOK)
default:
2022-06-18 17:51:37 +00:00
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("bogus probe method"))
}
}
2022-03-06 00:23:35 +00:00
// DERPBootstrapDNSHandler implements the /bootsrap-dns endpoint
// Described in https://github.com/tailscale/tailscale/issues/1405,
// this endpoint provides a way to help a client when it fails to start up
// because its DNS are broken.
// The initial implementation is here https://github.com/tailscale/tailscale/pull/1406
// They have a cache, but not clear if that is really necessary at Headscale, uh, scale.
2022-03-06 16:00:56 +00:00
// An example implementation is found here https://derp.tailscale.com/bootstrap-dns
2022-06-18 16:41:42 +00:00
func (h *Headscale) DERPBootstrapDNSHandler(
w http.ResponseWriter,
r *http.Request,
) {
2022-03-06 00:23:35 +00:00
dnsEntries := make(map[string][]net.IP)
resolvCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
2022-06-18 16:41:42 +00:00
var resolver net.Resolver
2022-03-06 00:23:35 +00:00
for _, region := range h.DERPMap.Regions {
for _, node := range region.Nodes { // we don't care if we override some nodes
2022-06-18 16:41:42 +00:00
addrs, err := resolver.LookupIP(resolvCtx, "ip", node.HostName)
2022-03-06 00:23:35 +00:00
if err != nil {
2022-04-15 16:27:57 +00:00
log.Trace().
Caller().
Err(err).
Msgf("bootstrap DNS lookup failed %q", node.HostName)
2022-03-06 16:35:54 +00:00
2022-03-06 00:23:35 +00:00
continue
}
dnsEntries[node.HostName] = addrs
}
}
2022-06-18 17:51:37 +00:00
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(dnsEntries)
}
2022-03-06 16:25:21 +00:00
// ServeSTUN starts a STUN server on the configured addr.
func (h *Headscale) ServeSTUN() {
2022-03-06 16:00:56 +00:00
packetConn, err := net.ListenPacket("udp", h.cfg.DERP.STUNAddr)
if err != nil {
log.Fatal().Msgf("failed to open STUN listener: %v", err)
}
2022-03-06 16:00:56 +00:00
log.Info().Msgf("STUN server started at %s", packetConn.LocalAddr())
2022-03-06 16:35:54 +00:00
udpConn, ok := packetConn.(*net.UDPConn)
if !ok {
log.Fatal().Msg("STUN listener is not a UDP listener")
}
serverSTUNListener(context.Background(), udpConn)
}
2022-03-06 16:35:54 +00:00
func serverSTUNListener(ctx context.Context, packetConn *net.UDPConn) {
var buf [64 << 10]byte
var (
2022-03-06 16:35:54 +00:00
bytesRead int
udpAddr *net.UDPAddr
err error
)
for {
2022-03-06 16:35:54 +00:00
bytesRead, udpAddr, err = packetConn.ReadFromUDP(buf[:])
if err != nil {
if ctx.Err() != nil {
return
}
2022-03-05 18:30:30 +00:00
log.Error().Caller().Err(err).Msgf("STUN ReadFrom")
time.Sleep(time.Second)
2022-03-06 16:35:54 +00:00
continue
}
2022-03-06 16:35:54 +00:00
log.Trace().Caller().Msgf("STUN request from %v", udpAddr)
pkt := buf[:bytesRead]
if !stun.Is(pkt) {
2022-03-08 11:11:51 +00:00
log.Trace().Caller().Msgf("UDP packet is not STUN")
continue
}
txid, err := stun.ParseBindingRequest(pkt)
if err != nil {
2022-03-08 11:11:51 +00:00
log.Trace().Caller().Err(err).Msgf("STUN parse error")
continue
}
2022-03-06 16:35:54 +00:00
res := stun.Response(txid, udpAddr.IP, uint16(udpAddr.Port))
_, err = packetConn.WriteTo(res, udpAddr)
if err != nil {
2022-03-08 11:11:51 +00:00
log.Trace().Caller().Err(err).Msgf("Issue writing to UDP")
2022-03-06 16:35:54 +00:00
continue
}
}
}