mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-15 20:19:20 +08:00
157 lines
3.3 KiB
Go
157 lines
3.3 KiB
Go
package headscale
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"gopkg.in/yaml.v2"
|
|
"tailscale.com/tailcfg"
|
|
)
|
|
|
|
func loadDERPMapFromPath(path string) (*tailcfg.DERPMap, error) {
|
|
derpFile, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer derpFile.Close()
|
|
var derpMap tailcfg.DERPMap
|
|
b, err := io.ReadAll(derpFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = yaml.Unmarshal(b, &derpMap)
|
|
|
|
return &derpMap, err
|
|
}
|
|
|
|
func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), HTTPReadTimeout)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, addr.String(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := http.Client{
|
|
Timeout: HTTPReadTimeout,
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var derpMap tailcfg.DERPMap
|
|
err = json.Unmarshal(body, &derpMap)
|
|
|
|
return &derpMap, err
|
|
}
|
|
|
|
// mergeDERPMaps naively merges a list of DERPMaps into a single
|
|
// DERPMap, it will _only_ look at the Regions, an integer.
|
|
// If a region exists in two of the given DERPMaps, the region
|
|
// form the _last_ DERPMap will be preserved.
|
|
// An empty DERPMap list will result in a DERPMap with no regions.
|
|
func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
|
|
result := tailcfg.DERPMap{
|
|
OmitDefaultRegions: false,
|
|
Regions: map[int]*tailcfg.DERPRegion{},
|
|
}
|
|
|
|
for _, derpMap := range derpMaps {
|
|
for id, region := range derpMap.Regions {
|
|
result.Regions[id] = region
|
|
}
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
func GetDERPMap(cfg DERPConfig) *tailcfg.DERPMap {
|
|
derpMaps := make([]*tailcfg.DERPMap, 0)
|
|
|
|
for _, path := range cfg.Paths {
|
|
log.Debug().
|
|
Str("func", "GetDERPMap").
|
|
Str("path", path).
|
|
Msg("Loading DERPMap from path")
|
|
derpMap, err := loadDERPMapFromPath(path)
|
|
if err != nil {
|
|
log.Error().
|
|
Str("func", "GetDERPMap").
|
|
Str("path", path).
|
|
Err(err).
|
|
Msg("Could not load DERP map from path")
|
|
|
|
break
|
|
}
|
|
|
|
derpMaps = append(derpMaps, derpMap)
|
|
}
|
|
|
|
for _, addr := range cfg.URLs {
|
|
derpMap, err := loadDERPMapFromURL(addr)
|
|
log.Debug().
|
|
Str("func", "GetDERPMap").
|
|
Str("url", addr.String()).
|
|
Msg("Loading DERPMap from path")
|
|
if err != nil {
|
|
log.Error().
|
|
Str("func", "GetDERPMap").
|
|
Str("url", addr.String()).
|
|
Err(err).
|
|
Msg("Could not load DERP map from path")
|
|
|
|
break
|
|
}
|
|
|
|
derpMaps = append(derpMaps, derpMap)
|
|
}
|
|
|
|
derpMap := mergeDERPMaps(derpMaps)
|
|
|
|
log.Trace().Interface("derpMap", derpMap).Msg("DERPMap loaded")
|
|
|
|
if len(derpMap.Regions) == 0 {
|
|
log.Warn().
|
|
Msg("DERP map is empty, not a single DERP map datasource was loaded correctly or contained a region")
|
|
}
|
|
|
|
return derpMap
|
|
}
|
|
|
|
func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
|
|
log.Info().
|
|
Dur("frequency", h.cfg.DERP.UpdateFrequency).
|
|
Msg("Setting up a DERPMap update worker")
|
|
ticker := time.NewTicker(h.cfg.DERP.UpdateFrequency)
|
|
|
|
for {
|
|
select {
|
|
case <-cancelChan:
|
|
return
|
|
|
|
case <-ticker.C:
|
|
log.Info().Msg("Fetching DERPMap updates")
|
|
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
|
if h.cfg.DERP.ServerEnabled {
|
|
h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region
|
|
}
|
|
|
|
h.setLastStateChangeToNow()
|
|
}
|
|
}
|
|
}
|