mirror of
https://github.com/juanfont/headscale.git
synced 2025-10-23 11:19:19 +02:00
Previously, nil regions were not properly handled. This change allows users to disable regions in DERPMaps. Particularly useful to disable some official regions.
167 lines
3.3 KiB
Go
167 lines
3.3 KiB
Go
package derp
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"encoding/json"
|
|
"hash/crc64"
|
|
"io"
|
|
"maps"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
|
"github.com/spf13/viper"
|
|
"gopkg.in/yaml.v3"
|
|
"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(), types.HTTPTimeout)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, addr.String(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := http.Client{
|
|
Timeout: types.HTTPTimeout,
|
|
}
|
|
|
|
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 {
|
|
maps.Copy(result.Regions, derpMap.Regions)
|
|
}
|
|
|
|
for id, region := range result.Regions {
|
|
if region == nil {
|
|
delete(result.Regions, id)
|
|
}
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
func GetDERPMap(cfg types.DERPConfig) (*tailcfg.DERPMap, error) {
|
|
var derpMaps []*tailcfg.DERPMap
|
|
if cfg.DERPMap != nil {
|
|
derpMaps = append(derpMaps, cfg.DERPMap)
|
|
}
|
|
|
|
for _, addr := range cfg.URLs {
|
|
derpMap, err := loadDERPMapFromURL(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
derpMaps = append(derpMaps, derpMap)
|
|
}
|
|
|
|
for _, path := range cfg.Paths {
|
|
derpMap, err := loadDERPMapFromPath(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
derpMaps = append(derpMaps, derpMap)
|
|
}
|
|
|
|
derpMap := mergeDERPMaps(derpMaps)
|
|
shuffleDERPMap(derpMap)
|
|
|
|
return derpMap, nil
|
|
}
|
|
|
|
func shuffleDERPMap(dm *tailcfg.DERPMap) {
|
|
if dm == nil || len(dm.Regions) == 0 {
|
|
return
|
|
}
|
|
|
|
for id, region := range dm.Regions {
|
|
if len(region.Nodes) == 0 {
|
|
continue
|
|
}
|
|
|
|
dm.Regions[id] = shuffleRegionNoClone(region)
|
|
}
|
|
}
|
|
|
|
var crc64Table = crc64.MakeTable(crc64.ISO)
|
|
|
|
var (
|
|
derpRandomOnce sync.Once
|
|
derpRandomInst *rand.Rand
|
|
derpRandomMu sync.RWMutex
|
|
)
|
|
|
|
func derpRandom() *rand.Rand {
|
|
derpRandomOnce.Do(func() {
|
|
seed := cmp.Or(viper.GetString("dns.base_domain"), time.Now().String())
|
|
rnd := rand.New(rand.NewSource(0))
|
|
rnd.Seed(int64(crc64.Checksum([]byte(seed), crc64Table)))
|
|
derpRandomInst = rnd
|
|
})
|
|
return derpRandomInst
|
|
}
|
|
|
|
func resetDerpRandomForTesting() {
|
|
derpRandomMu.Lock()
|
|
defer derpRandomMu.Unlock()
|
|
derpRandomOnce = sync.Once{}
|
|
derpRandomInst = nil
|
|
}
|
|
|
|
func shuffleRegionNoClone(r *tailcfg.DERPRegion) *tailcfg.DERPRegion {
|
|
derpRandom().Shuffle(len(r.Nodes), reflect.Swapper(r.Nodes))
|
|
return r
|
|
}
|