move derp_server to derp server module

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2023-06-06 11:09:48 +02:00 committed by Kristoffer Dalby
parent 8c4c4c8633
commit 80ea87c032
2 changed files with 80 additions and 59 deletions

View File

@ -25,6 +25,7 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/db" "github.com/juanfont/headscale/hscontrol/db"
"github.com/juanfont/headscale/hscontrol/derp" "github.com/juanfont/headscale/hscontrol/derp"
derpServer "github.com/juanfont/headscale/hscontrol/derp/server"
"github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
@ -79,7 +80,7 @@ type Headscale struct {
noisePrivateKey *key.MachinePrivate noisePrivateKey *key.MachinePrivate
DERPMap *tailcfg.DERPMap DERPMap *tailcfg.DERPMap
DERPServer *DERPServer DERPServer *derpServer.DERPServer
ACLPolicy *policy.ACLPolicy ACLPolicy *policy.ACLPolicy
@ -202,7 +203,8 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
} }
if cfg.DERP.ServerEnabled { if cfg.DERP.ServerEnabled {
embeddedDERPServer, err := app.NewDERPServer() // TODO(kradalby): replace this key with a dedicated DERP key.
embeddedDERPServer, err := derpServer.NewDERPServer(cfg.ServerURL, key.NodePrivate(*privateKey), &cfg.DERP)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -237,7 +239,7 @@ func (h *Headscale) expireExpiredMachines(milliSeconds int64) {
} }
// scheduledDERPMapUpdateWorker refreshes the DERPMap stored on the global object // scheduledDERPMapUpdateWorker refreshes the DERPMap stored on the global object
// at a set interval // at a set interval.
func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) { func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
log.Info(). log.Info().
Dur("frequency", h.cfg.DERP.UpdateFrequency). Dur("frequency", h.cfg.DERP.UpdateFrequency).
@ -253,7 +255,8 @@ func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
log.Info().Msg("Fetching DERPMap updates") log.Info().Msg("Fetching DERPMap updates")
h.DERPMap = derp.GetDERPMap(h.cfg.DERP) h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
if h.cfg.DERP.ServerEnabled { if h.cfg.DERP.ServerEnabled {
h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region region, _ := h.DERPServer.GenerateRegion()
h.DERPMap.Regions[region.RegionID] = &region
} }
h.setLastStateChangeToNow() h.setLastStateChangeToNow()
@ -456,9 +459,9 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router {
Methods(http.MethodGet) Methods(http.MethodGet)
if h.cfg.DERP.ServerEnabled { if h.cfg.DERP.ServerEnabled {
router.HandleFunc("/derp", h.DERPHandler) router.HandleFunc("/derp", h.DERPServer.DERPHandler)
router.HandleFunc("/derp/probe", h.DERPProbeHandler) router.HandleFunc("/derp/probe", derpServer.DERPProbeHandler)
router.HandleFunc("/bootstrap-dns", h.DERPBootstrapDNSHandler) router.HandleFunc("/bootstrap-dns", derpServer.DERPBootstrapDNSHandler(h.DERPMap))
} }
apiRouter := router.PathPrefix("/api").Subrouter() apiRouter := router.PathPrefix("/api").Subrouter()
@ -483,8 +486,14 @@ func (h *Headscale) Serve() error {
return errSTUNAddressNotSet return errSTUNAddressNotSet
} }
h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region region, err := h.DERPServer.GenerateRegion()
go h.ServeSTUN() if err != nil {
return err
}
h.DERPMap.Regions[region.RegionID] = &region
go h.DERPServer.ServeSTUN()
} }
if h.cfg.DERP.AutoUpdate { if h.cfg.DERP.AutoUpdate {

View File

@ -1,4 +1,4 @@
package hscontrol package server
import ( import (
"context" "context"
@ -12,6 +12,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"tailscale.com/derp" "tailscale.com/derp"
"tailscale.com/net/stun" "tailscale.com/net/stun"
@ -26,23 +27,30 @@ import (
const fastStartHeader = "Derp-Fast-Start" const fastStartHeader = "Derp-Fast-Start"
type DERPServer struct { type DERPServer struct {
serverURL string
key key.NodePrivate
cfg *types.DERPConfig
tailscaleDERP *derp.Server tailscaleDERP *derp.Server
region tailcfg.DERPRegion
} }
func (h *Headscale) NewDERPServer() (*DERPServer, error) { func NewDERPServer(
serverURL string,
derpKey key.NodePrivate,
cfg *types.DERPConfig,
) (*DERPServer, error) {
log.Trace().Caller().Msg("Creating new embedded DERP server") log.Trace().Caller().Msg("Creating new embedded DERP server")
server := derp.NewServer(key.NodePrivate(*h.privateKey2019), log.Info().Msgf) server := derp.NewServer(derpKey, log.Debug().Msgf)
region, err := h.generateRegionLocalDERP()
if err != nil {
return nil, err
}
return &DERPServer{server, region}, nil return &DERPServer{
serverURL: serverURL,
key: derpKey,
cfg: cfg,
tailscaleDERP: server,
}, nil
} }
func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) { func (d *DERPServer) GenerateRegion() (tailcfg.DERPRegion, error) {
serverURL, err := url.Parse(h.cfg.ServerURL) serverURL, err := url.Parse(d.serverURL)
if err != nil { if err != nil {
return tailcfg.DERPRegion{}, err return tailcfg.DERPRegion{}, err
} }
@ -65,21 +73,21 @@ func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) {
} }
localDERPregion := tailcfg.DERPRegion{ localDERPregion := tailcfg.DERPRegion{
RegionID: h.cfg.DERP.ServerRegionID, RegionID: d.cfg.ServerRegionID,
RegionCode: h.cfg.DERP.ServerRegionCode, RegionCode: d.cfg.ServerRegionCode,
RegionName: h.cfg.DERP.ServerRegionName, RegionName: d.cfg.ServerRegionName,
Avoid: false, Avoid: false,
Nodes: []*tailcfg.DERPNode{ Nodes: []*tailcfg.DERPNode{
{ {
Name: fmt.Sprintf("%d", h.cfg.DERP.ServerRegionID), Name: fmt.Sprintf("%d", d.cfg.ServerRegionID),
RegionID: h.cfg.DERP.ServerRegionID, RegionID: d.cfg.ServerRegionID,
HostName: host, HostName: host,
DERPPort: port, DERPPort: port,
}, },
}, },
} }
_, portSTUNStr, err := net.SplitHostPort(h.cfg.DERP.STUNAddr) _, portSTUNStr, err := net.SplitHostPort(d.cfg.STUNAddr)
if err != nil { if err != nil {
return tailcfg.DERPRegion{}, err return tailcfg.DERPRegion{}, err
} }
@ -94,7 +102,7 @@ func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) {
return localDERPregion, nil return localDERPregion, nil
} }
func (h *Headscale) DERPHandler( func (d *DERPServer) DERPHandler(
writer http.ResponseWriter, writer http.ResponseWriter,
req *http.Request, req *http.Request,
) { ) {
@ -156,7 +164,7 @@ func (h *Headscale) DERPHandler(
log.Trace().Caller().Msgf("Hijacked connection from %v", req.RemoteAddr) log.Trace().Caller().Msgf("Hijacked connection from %v", req.RemoteAddr)
if !fastStart { if !fastStart {
pubKey := h.privateKey2019.Public() pubKey := d.key.Public()
pubKeyStr, _ := pubKey.MarshalText() //nolint pubKeyStr, _ := pubKey.MarshalText() //nolint
fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+ fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
"Upgrade: DERP\r\n"+ "Upgrade: DERP\r\n"+
@ -167,12 +175,12 @@ func (h *Headscale) DERPHandler(
string(pubKeyStr)) string(pubKeyStr))
} }
h.DERPServer.tailscaleDERP.Accept(req.Context(), netConn, conn, netConn.RemoteAddr().String()) d.tailscaleDERP.Accept(req.Context(), netConn, conn, netConn.RemoteAddr().String())
} }
// DERPProbeHandler is the endpoint that js/wasm clients hit to measure // DERPProbeHandler is the endpoint that js/wasm clients hit to measure
// DERP latency, since they can't do UDP STUN queries. // DERP latency, since they can't do UDP STUN queries.
func (h *Headscale) DERPProbeHandler( func DERPProbeHandler(
writer http.ResponseWriter, writer http.ResponseWriter,
req *http.Request, req *http.Request,
) { ) {
@ -199,43 +207,47 @@ func (h *Headscale) DERPProbeHandler(
// The initial implementation is here https://github.com/tailscale/tailscale/pull/1406 // 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. // They have a cache, but not clear if that is really necessary at Headscale, uh, scale.
// An example implementation is found here https://derp.tailscale.com/bootstrap-dns // An example implementation is found here https://derp.tailscale.com/bootstrap-dns
func (h *Headscale) DERPBootstrapDNSHandler( func DERPBootstrapDNSHandler(
writer http.ResponseWriter, derpMap *tailcfg.DERPMap,
req *http.Request, ) func(http.ResponseWriter, *http.Request) {
) { return func(
dnsEntries := make(map[string][]net.IP) writer http.ResponseWriter,
req *http.Request,
) {
dnsEntries := make(map[string][]net.IP)
resolvCtx, cancel := context.WithTimeout(req.Context(), time.Minute) resolvCtx, cancel := context.WithTimeout(req.Context(), time.Minute)
defer cancel() defer cancel()
var resolver net.Resolver var resolver net.Resolver
for _, region := range h.DERPMap.Regions { for _, region := range derpMap.Regions {
for _, node := range region.Nodes { // we don't care if we override some nodes for _, node := range region.Nodes { // we don't care if we override some nodes
addrs, err := resolver.LookupIP(resolvCtx, "ip", node.HostName) addrs, err := resolver.LookupIP(resolvCtx, "ip", node.HostName)
if err != nil { if err != nil {
log.Trace(). log.Trace().
Caller(). Caller().
Err(err). Err(err).
Msgf("bootstrap DNS lookup failed %q", node.HostName) Msgf("bootstrap DNS lookup failed %q", node.HostName)
continue continue
}
dnsEntries[node.HostName] = addrs
} }
dnsEntries[node.HostName] = addrs
} }
} writer.Header().Set("Content-Type", "application/json")
writer.Header().Set("Content-Type", "application/json") writer.WriteHeader(http.StatusOK)
writer.WriteHeader(http.StatusOK) err := json.NewEncoder(writer).Encode(dnsEntries)
err := json.NewEncoder(writer).Encode(dnsEntries) if err != nil {
if err != nil { log.Error().
log.Error(). Caller().
Caller(). Err(err).
Err(err). Msg("Failed to write response")
Msg("Failed to write response") }
} }
} }
// ServeSTUN starts a STUN server on the configured addr. // ServeSTUN starts a STUN server on the configured addr.
func (h *Headscale) ServeSTUN() { func (d *DERPServer) ServeSTUN() {
packetConn, err := net.ListenPacket("udp", h.cfg.DERP.STUNAddr) packetConn, err := net.ListenPacket("udp", d.cfg.STUNAddr)
if err != nil { if err != nil {
log.Fatal().Msgf("failed to open STUN listener: %v", err) log.Fatal().Msgf("failed to open STUN listener: %v", err)
} }