mirror of https://github.com/coder/coder.git
124 lines
2.3 KiB
Go
124 lines
2.3 KiB
Go
package devtunnel
|
|
|
|
import (
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-ping/ping"
|
|
"golang.org/x/exp/slices"
|
|
"golang.org/x/sync/errgroup"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/coderd/util/slice"
|
|
"github.com/coder/coder/v2/cryptorand"
|
|
)
|
|
|
|
type Region struct {
|
|
ID int
|
|
LocationName string
|
|
Nodes []Node
|
|
}
|
|
|
|
type Node struct {
|
|
ID int `json:"id"`
|
|
RegionID int `json:"region_id"`
|
|
HostnameHTTPS string `json:"hostname_https"`
|
|
|
|
AvgLatency time.Duration `json:"-"`
|
|
}
|
|
|
|
var Regions = []Region{
|
|
{
|
|
ID: 0,
|
|
LocationName: "US East Pittsburgh",
|
|
Nodes: []Node{
|
|
{
|
|
ID: 1,
|
|
RegionID: 0,
|
|
HostnameHTTPS: "pit-1.try.coder.app",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Nodes returns a list of nodes to use for the tunnel. It will pick a random
|
|
// node from each region.
|
|
//
|
|
// If a customNode is provided, it will be returned as the only node with ID
|
|
// 9999.
|
|
func Nodes(customTunnelHost string) ([]Node, error) {
|
|
nodes := []Node{}
|
|
|
|
if customTunnelHost != "" {
|
|
return []Node{
|
|
{
|
|
ID: 9999,
|
|
RegionID: 9999,
|
|
HostnameHTTPS: customTunnelHost,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
for _, region := range Regions {
|
|
// Pick a random node from each region.
|
|
i, err := cryptorand.Intn(len(region.Nodes))
|
|
if err != nil {
|
|
return []Node{}, err
|
|
}
|
|
nodes = append(nodes, region.Nodes[i])
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
// FindClosestNode pings each node and returns the one with the lowest latency.
|
|
func FindClosestNode(nodes []Node) (Node, error) {
|
|
if len(nodes) == 0 {
|
|
return Node{}, xerrors.New("no wgtunnel nodes")
|
|
}
|
|
|
|
// Copy the nodes so we don't mutate the original.
|
|
nodes = append([]Node{}, nodes...)
|
|
|
|
var (
|
|
nodesMu sync.Mutex
|
|
eg = errgroup.Group{}
|
|
)
|
|
for i, node := range nodes {
|
|
i, node := i, node
|
|
eg.Go(func() error {
|
|
pinger, err := ping.NewPinger(node.HostnameHTTPS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
pinger.SetPrivileged(true)
|
|
}
|
|
|
|
pinger.Count = 5
|
|
pinger.Timeout = 5 * time.Second
|
|
err = pinger.Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nodesMu.Lock()
|
|
nodes[i].AvgLatency = pinger.Statistics().AvgRtt
|
|
nodesMu.Unlock()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
err := eg.Wait()
|
|
if err != nil {
|
|
return Node{}, err
|
|
}
|
|
|
|
slices.SortFunc(nodes, func(a, b Node) int {
|
|
return slice.Ascending(a.AvgLatency, b.AvgLatency)
|
|
})
|
|
return nodes[0], nil
|
|
}
|