mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			170 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			3.4 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.Mutex
 | |
| )
 | |
| 
 | |
| func derpRandom() *rand.Rand {
 | |
| 	derpRandomMu.Lock()
 | |
| 	defer derpRandomMu.Unlock()
 | |
| 
 | |
| 	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
 | |
| }
 |