feat: embedded derp

This commit is contained in:
Johan Siebens
2024-02-28 09:53:19 +01:00
parent 27c6a1fa12
commit 248b75cd77
24 changed files with 914 additions and 938 deletions
+1 -65
View File
@@ -6,9 +6,7 @@ import (
"github.com/bufbuild/connect-go"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
"github.com/spf13/cobra"
"github.com/tailscale/hujson"
"gopkg.in/yaml.v2"
"os"
"tailscale.com/tailcfg"
)
@@ -19,8 +17,6 @@ func systemCommand() *cobra.Command {
}
command.AddCommand(getDefaultDERPMap())
command.AddCommand(setDefaultDERPMap())
command.AddCommand(resetDefaultDERPMap())
return command
}
@@ -28,7 +24,7 @@ func systemCommand() *cobra.Command {
func getDefaultDERPMap() *cobra.Command {
command, tc := prepareCommand(false, &cobra.Command{
Use: "get-derp-map",
Short: "Get the DERP Map configuration",
Short: "Get the default DERP Map configuration",
SilenceUsage: true,
})
@@ -72,63 +68,3 @@ func getDefaultDERPMap() *cobra.Command {
return command
}
func setDefaultDERPMap() *cobra.Command {
command, tc := prepareCommand(false, &cobra.Command{
Use: "set-derp-map",
Short: "Set the DERP Map configuration",
SilenceUsage: true,
})
var file string
command.Flags().StringVar(&file, "file", "", "Path to json file with the DERP Map configuration")
command.RunE = func(cmd *cobra.Command, args []string) error {
content, err := os.ReadFile(file)
if err != nil {
return err
}
rawJson, err := hujson.Standardize(content)
if err != nil {
return err
}
resp, err := tc.Client().SetDefaultDERPMap(cmd.Context(), connect.NewRequest(&api.SetDefaultDERPMapRequest{Value: rawJson}))
if err != nil {
return err
}
var derpMap tailcfg.DERPMap
if err := json.Unmarshal(resp.Msg.Value, &derpMap); err != nil {
return err
}
fmt.Println("DERP Map updated successfully")
return nil
}
return command
}
func resetDefaultDERPMap() *cobra.Command {
command, tc := prepareCommand(false, &cobra.Command{
Use: "reset-derp-map",
Short: "Reset the DERP Map to the default configuration",
SilenceUsage: true,
})
command.RunE = func(cmd *cobra.Command, args []string) error {
if _, err := tc.Client().ResetDefaultDERPMap(cmd.Context(), connect.NewRequest(&api.ResetDefaultDERPMapRequest{})); err != nil {
return err
}
fmt.Println("DERP Map updated successfully")
return nil
}
return command
}
+94 -2
View File
@@ -14,6 +14,7 @@ import (
"net/url"
"os"
"path/filepath"
"tailscale.com/tailcfg"
tkey "tailscale.com/types/key"
"time"
)
@@ -102,6 +103,7 @@ func defaultConfig() *Config {
return &Config{
WebListenAddr: ":8080",
MetricsListenAddr: ":9091",
StunListenAddr: ":3478",
Database: Database{
Type: "sqlite",
Url: "./ionscale.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(ON)",
@@ -120,6 +122,14 @@ func defaultConfig() *Config {
DNS: DNS{
MagicDNSSuffix: defaultMagicDNSSuffix,
},
DERP: DERP{
Server: DERPServer{
Disabled: false,
RegionID: 1000,
RegionCode: "ionscale",
RegionName: "ionscale Embedded DERP",
},
},
Logging: Logging{
Level: "info",
},
@@ -134,17 +144,25 @@ type ServerKeys struct {
type Config struct {
WebListenAddr string `yaml:"web_listen_addr,omitempty" env:"WEB_LISTEN_ADDR"`
StunListenAddr string `yaml:"stun_listen_addr,omitempty" env:"STUN_LISTEN_ADDR"`
MetricsListenAddr string `yaml:"metrics_listen_addr,omitempty" env:"METRICS_LISTEN_ADDR"`
WebPublicAddr string `yaml:"web_public_addr,omitempty" env:"WEB_PUBLIC_ADDR"`
StunPublicAddr string `yaml:"stun_public_addr,omitempty" env:"STUN_PUBLIC_ADDR"`
Tls Tls `yaml:"tls,omitempty" envPrefix:"TLS_"`
PollNet PollNet `yaml:"poll_net,omitempty" envPrefix:"POLL_NET_"`
Keys Keys `yaml:"keys,omitempty" envPrefix:"KEYS_"`
Database Database `yaml:"database,omitempty" envPrefix:"DB_"`
Auth Auth `yaml:"auth,omitempty" envPrefix:"AUTH_"`
DNS DNS `yaml:"dns,omitempty"`
DERP DERP `yaml:"derp,omitempty" envPrefix:"DERP_"`
Logging Logging `yaml:"logging,omitempty" envPrefix:"LOGGING_"`
WebPublicUrl *url.URL `yaml:"-"`
stunHost string
stunPort int
derpHost string
derpPort int
}
type Tls struct {
@@ -211,13 +229,38 @@ type SystemAdminPolicy struct {
Filters []string `yaml:"filters,omitempty"`
}
type DERP struct {
Server DERPServer `yaml:"server,omitempty"`
Sources []string `yaml:"sources,omitempty"`
}
type DERPServer struct {
Disabled bool `yaml:"disabled,omitempty"`
RegionID int `yaml:"region_id,omitempty"`
RegionCode string `yaml:"region_code,omitempty"`
RegionName string `yaml:"region_name,omitempty"`
}
func (c *Config) Validate() (*Config, error) {
publicWebUrl, err := publicAddrToUrl(c.WebPublicAddr)
publicWebUrl, webHost, webPort, err := validatePublicAddr(c.WebPublicAddr)
if err != nil {
return nil, err
return nil, fmt.Errorf("web public addr: %w", err)
}
c.WebPublicUrl = publicWebUrl
c.derpHost = webHost
c.derpPort = webPort
if !c.DERP.Server.Disabled {
_, stunHost, stunPort, err := validatePublicAddr(c.StunPublicAddr)
if err != nil {
return nil, fmt.Errorf("stun public addr: %w", err)
}
c.stunHost = stunHost
c.stunPort = stunPort
}
return c, nil
}
@@ -264,3 +307,52 @@ func (c *Config) ReadServerKeys(defaultKeys *domain.ControlKeys) (*ServerKeys, e
return keys, nil
}
func (c *Config) DefaultDERPMap() *tailcfg.DERPMap {
if c.derpHost == c.stunHost {
return &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
c.DERP.Server.RegionID: {
RegionID: c.DERP.Server.RegionID,
RegionCode: c.DERP.Server.RegionCode,
RegionName: c.DERP.Server.RegionName,
Nodes: []*tailcfg.DERPNode{
{
RegionID: c.DERP.Server.RegionID,
Name: "ionscale",
HostName: c.derpHost,
DERPPort: c.derpPort,
STUNPort: c.stunPort,
},
},
},
},
}
}
return &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
c.DERP.Server.RegionID: {
RegionID: c.DERP.Server.RegionID,
RegionCode: c.DERP.Server.RegionCode,
RegionName: c.DERP.Server.RegionName,
Nodes: []*tailcfg.DERPNode{
{
RegionID: c.DERP.Server.RegionID,
Name: "stun",
HostName: c.stunHost,
STUNOnly: true,
STUNPort: c.stunPort,
},
{
RegionID: c.DERP.Server.RegionID,
Name: "derp",
HostName: c.derpHost,
DERPPort: c.derpPort,
STUNPort: -1,
},
},
},
},
}
}
+12 -6
View File
@@ -5,6 +5,7 @@ import (
"net"
"net/url"
"os"
"strconv"
"strings"
)
@@ -24,7 +25,7 @@ func GetString(key, defaultValue string) string {
return defaultValue
}
func publicAddrToUrl(addr string) (*url.URL, error) {
func validatePublicAddr(addr string) (*url.URL, string, int, error) {
scheme := "https"
if strings.HasPrefix(addr, "http://") {
@@ -37,14 +38,19 @@ func publicAddrToUrl(addr string) (*url.URL, error) {
addr = strings.TrimPrefix(addr, "https://")
}
host, port, err := net.SplitHostPort(addr)
host, portS, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("invalid public addr")
return nil, "", -1, fmt.Errorf("invalid")
}
if (port == "443" && scheme == "https") || (port == "80" && scheme == "http") || port == "" {
return &url.URL{Scheme: scheme, Host: host}, nil
port, err := strconv.Atoi(portS)
if err != nil {
return nil, "", 0, fmt.Errorf("invalid")
}
return &url.URL{Scheme: scheme, Host: fmt.Sprintf("%s:%s", host, port)}, nil
if (port == 443 && scheme == "https") || (port == 80 && scheme == "http") {
return &url.URL{Scheme: scheme, Host: host}, host, port, nil
}
return &url.URL{Scheme: scheme, Host: fmt.Sprintf("%s:%s", host, port)}, host, port, nil
}
+1 -1
View File
@@ -28,7 +28,7 @@ func TestPublicAddrToUrl(t *testing.T) {
for _, p := range parameters {
t.Run(fmt.Sprintf("Testing [%v]", p.input), func(t *testing.T) {
url, err := publicAddrToUrl(p.input)
url, err := validatePublicAddr(p.input)
require.Equal(t, p.expected, url)
require.Equal(t, p.err, err)
})
+63
View File
@@ -0,0 +1,63 @@
package derp
import (
"encoding/json"
"github.com/hashicorp/go-getter"
"github.com/hashicorp/go-multierror"
"github.com/jsiebens/ionscale/internal/config"
"os"
"tailscale.com/tailcfg"
)
func LoadDERPSources(c *config.Config) (*tailcfg.DERPMap, error) {
derpMap := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{},
}
var merr *multierror.Error
for _, src := range c.DERP.Sources {
dm, err := loadDERPSource(src)
if err != nil {
merr = multierror.Append(merr, err)
continue
}
for id, r := range dm.Regions {
derpMap.Regions[id] = r
}
}
if !c.DERP.Server.Disabled {
dm := c.DefaultDERPMap()
for id, r := range dm.Regions {
derpMap.Regions[id] = r
}
}
return derpMap, merr.ErrorOrNil()
}
func loadDERPSource(src string) (*tailcfg.DERPMap, error) {
temp, err := os.CreateTemp(os.TempDir(), "derp-*.json")
if err != nil {
return nil, err
}
defer os.Remove(temp.Name())
if err := getter.Get(temp.Name(), src, getter.WithMode(getter.ClientModeFile)); err != nil {
return nil, err
}
content, err := os.ReadFile(temp.Name())
if err != nil {
return nil, err
}
var dm tailcfg.DERPMap
if err := json.Unmarshal(content, &dm); err != nil {
return nil, err
}
return &dm, nil
}
+34
View File
@@ -5,16 +5,50 @@ import (
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/jsiebens/ionscale/internal/util"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"sync"
"tailscale.com/tailcfg"
)
var (
_defaultDERPMapMu sync.RWMutex
_defaultDERPMap = WrapDERPMap(tailcfg.DERPMap{})
)
func SetDefaultDERPMap(v *tailcfg.DERPMap) {
if v == nil {
return
}
_defaultDERPMapMu.Lock()
defer _defaultDERPMapMu.Unlock()
_defaultDERPMap = WrapDERPMap(*v)
}
func GetDefaultDERPMap() DERPMap {
_defaultDERPMapMu.RLock()
defer _defaultDERPMapMu.RUnlock()
return _defaultDERPMap
}
type DERPMap struct {
Checksum string
DERPMap tailcfg.DERPMap
}
func (d DERPMap) GetDERPMap(_ context.Context) (*DERPMap, error) {
return &d, nil
}
func WrapDERPMap(d tailcfg.DERPMap) DERPMap {
return DERPMap{
Checksum: util.Checksum(d),
DERPMap: d,
}
}
func (hi *DERPMap) Scan(destination interface{}) error {
switch value := destination.(type) {
case []byte:
+2 -54
View File
@@ -2,14 +2,8 @@ package domain
import (
"context"
"encoding/json"
"github.com/jsiebens/ionscale/internal/util"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"net/http"
"sync"
"tailscale.com/tailcfg"
"time"
)
type Repository interface {
@@ -30,22 +24,17 @@ type Repository interface {
GetJSONWebKeySet(ctx context.Context) (*JSONWebKeys, error)
SetJSONWebKeySet(ctx context.Context, keys *JSONWebKeys) error
GetDERPMap(ctx context.Context) (*DERPMap, error)
SetDERPMap(ctx context.Context, v *DERPMap) error
Transaction(func(rp Repository) error) error
}
func NewRepository(db *gorm.DB) Repository {
return &repository{
db: db,
defaultDERPMap: &derpMapCache{},
db: db,
}
}
type repository struct {
db *gorm.DB
defaultDERPMap *derpMapCache
db *gorm.DB
}
func (r *repository) withContext(ctx context.Context) *gorm.DB {
@@ -57,44 +46,3 @@ func (r *repository) Transaction(action func(Repository) error) error {
return action(NewRepository(tx))
})
}
type derpMapCache struct {
sync.RWMutex
value *DERPMap
}
func (d *derpMapCache) Get() (*DERPMap, error) {
d.RLock()
if d.value != nil {
d.RUnlock()
return d.value, nil
}
d.RUnlock()
d.Lock()
defer d.Unlock()
getJson := func(url string, target interface{}) error {
c := http.Client{Timeout: 5 * time.Second}
r, err := c.Get(url)
if err != nil {
return err
}
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(target)
}
m := &tailcfg.DERPMap{}
if err := getJson("https://controlplane.tailscale.com/derpmap/default", m); err != nil {
return nil, err
}
d.value = &DERPMap{
Checksum: util.Checksum(m),
DERPMap: *m,
}
return d.value, nil
}
-24
View File
@@ -81,30 +81,6 @@ func (r *repository) SetJSONWebKeySet(ctx context.Context, v *JSONWebKeys) error
return r.setServerConfig(ctx, jwksConfigKey, v)
}
func (r *repository) GetDERPMap(ctx context.Context) (*DERPMap, error) {
var m DERPMap
err := r.getServerConfig(ctx, derpMapConfigKey, &m)
if errors.Is(err, gorm.ErrRecordNotFound) {
return r.defaultDERPMap.Get()
}
if m.Checksum == "" {
return r.defaultDERPMap.Get()
}
if err != nil {
return nil, err
}
return &m, nil
}
func (r *repository) SetDERPMap(ctx context.Context, v *DERPMap) error {
return r.setServerConfig(ctx, "derp_map", v)
}
func (r *repository) getServerConfig(ctx context.Context, s configKey, v interface{}) error {
var m ServerConfig
tx := r.withContext(ctx).Take(&m, "key = ?", s)
+46
View File
@@ -0,0 +1,46 @@
package handlers
import (
"fmt"
"github.com/labstack/echo/v4"
"go.uber.org/zap"
"net/http"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/types/key"
)
func NewDERPHandler() *DERPHandlers {
logger := zap.L().Named("derp")
return &DERPHandlers{
s: derp.NewServer(key.NewNode(), func(format string, args ...any) {
logger.Debug(fmt.Sprintf(format, args...))
}),
}
}
type DERPHandlers struct {
s *derp.Server
}
func (h *DERPHandlers) Handler(c echo.Context) error {
derphttp.Handler(h.s).ServeHTTP(c.Response(), c.Request())
return nil
}
func (h *DERPHandlers) LatencyCheck(c echo.Context) error {
return c.String(http.StatusOK, "")
}
func (h *DERPHandlers) DebugTraffic(c echo.Context) error {
h.s.ServeDebugTraffic(c.Response(), c.Request())
return nil
}
func (h *DERPHandlers) DebugCheck(c echo.Context) error {
if err := h.s.ConsistencyCheck(); err != nil {
return err
}
return c.String(http.StatusOK, "DERP Server ConsistencyCheck okay")
}
+1 -1
View File
@@ -61,7 +61,7 @@ func (h *PollNetMapper) CreateMapResponse(ctx context.Context, delta bool) (*Map
return nil, err
}
derpMap, err := m.Tailnet.GetDERPMap(ctx, h.repository)
derpMap, err := m.Tailnet.GetDERPMap(ctx, domain.GetDefaultDERPMap())
if err != nil {
return nil, err
}
+58 -6
View File
@@ -10,10 +10,12 @@ import (
"github.com/jsiebens/ionscale/internal/config"
"github.com/jsiebens/ionscale/internal/core"
"github.com/jsiebens/ionscale/internal/database"
"github.com/jsiebens/ionscale/internal/derp"
"github.com/jsiebens/ionscale/internal/dns"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/handlers"
"github.com/jsiebens/ionscale/internal/service"
"github.com/jsiebens/ionscale/internal/stunserver"
"github.com/jsiebens/ionscale/internal/templates"
"github.com/labstack/echo-contrib/echoprometheus"
"github.com/labstack/echo-contrib/pprof"
@@ -51,6 +53,13 @@ func Start(ctx context.Context, c *config.Config) error {
return err
}
derpMap, err := derp.LoadDERPSources(c)
if err != nil {
logger.Warn("not all derp sources are read successfully", zap.Error(err))
}
domain.SetDefaultDERPMap(derpMap)
httpLogger := logger.Named("http")
dbLogger := logger.Named("db")
@@ -168,6 +177,16 @@ func Start(ctx context.Context, c *config.Config) error {
webMux.GET("/a/success", authenticationHandlers.Success, csrf)
webMux.GET("/a/error", authenticationHandlers.Error, csrf)
if !c.DERP.Server.Disabled {
derpHandlers := handlers.NewDERPHandler()
metricsMux.GET("/debug/derp/traffic", derpHandlers.DebugTraffic)
metricsMux.GET("/debug/derp/check", derpHandlers.DebugCheck)
webMux.GET("/derp", derpHandlers.Handler)
webMux.GET("/derp/latency-check", derpHandlers.LatencyCheck)
}
webL, err := webListener(c)
if err != nil {
return logError(err)
@@ -178,6 +197,11 @@ func Start(ctx context.Context, c *config.Config) error {
return logError(err)
}
stunL, err := stunListener(c)
if err != nil {
return logError(err)
}
errorLog, err := zap.NewStdLogAt(logger, zap.DebugLevel)
if err != nil {
return logError(err)
@@ -185,6 +209,7 @@ func Start(ctx context.Context, c *config.Config) error {
webServer := &http.Server{ErrorLog: errorLog, Handler: h2c.NewHandler(webMux, &http2.Server{})}
metricsServer := &http.Server{ErrorLog: errorLog, Handler: metricsMux}
stunServer := stunserver.New(stunL)
g, gCtx := errgroup.WithContext(ctx)
@@ -193,20 +218,34 @@ func Start(ctx context.Context, c *config.Config) error {
logger.Sugar().Infow("Shutting down ionscale server")
shutdownHttpServer(metricsServer)
shutdownHttpServer(webServer)
_ = stunServer.Shutdown()
}()
g.Go(func() error { return serveHttp(webServer, webL) })
g.Go(func() error { return serveHttp(metricsServer, metricsL) })
g.Go(func() error { return stunServer.Serve() })
fields := []zap.Field{
zap.String("url", c.WebPublicUrl.String()),
zap.String("addr", c.WebListenAddr),
zap.String("metrics_addr", c.MetricsListenAddr),
}
if !c.DERP.Server.Disabled {
fields = append(fields, zap.String("stun_addr", c.StunListenAddr))
} else {
logger.Warn("Embedded DERP is disabled")
}
if c.Tls.AcmeEnabled {
logger.Sugar().Infow("TLS is enabled with ACME", "domain", c.WebPublicUrl.Hostname())
logger.Sugar().Infow("Server is running", "addr", c.WebListenAddr, "metrics_addr", c.MetricsListenAddr, "url", c.WebPublicUrl)
logger.Info("TLS is enabled with ACME", zap.String("domain", c.WebPublicUrl.Hostname()))
logger.Info("Server is running", fields...)
} else if !c.Tls.Disable {
logger.Sugar().Infow("TLS is enabled", "cert", c.Tls.CertFile)
logger.Sugar().Infow("Server is running", "addr", c.WebListenAddr, "metrics_addr", c.MetricsListenAddr, "url", c.WebPublicUrl)
logger.Info("TLS is enabled", zap.String("cert", c.Tls.CertFile))
logger.Info("Server is running", fields...)
} else {
logger.Sugar().Warnw("TLS is disabled")
logger.Sugar().Infow("Server is running", "addr", c.WebListenAddr, "metrics_addr", c.MetricsListenAddr, "url", c.WebPublicUrl)
logger.Warn("TLS is disabled")
logger.Info("Server is running", fields...)
}
return g.Wait()
@@ -280,6 +319,19 @@ func metricsListener(config *config.Config) (net.Listener, error) {
return net.Listen("tcp", config.MetricsListenAddr)
}
func stunListener(config *config.Config) (*net.UDPConn, error) {
if config.DERP.Server.Disabled {
return nil, nil
}
addr, err := net.ResolveUDPAddr("udp", config.StunListenAddr)
if err != nil {
return nil, err
}
return net.ListenUDP("udp", addr)
}
func setupLogging(config config.Logging) (*zap.Logger, error) {
level, err := zap.ParseAtomicLevel(config.Level)
if err != nil {
+1 -62
View File
@@ -6,9 +6,7 @@ import (
"fmt"
"github.com/bufbuild/connect-go"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/util"
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
"tailscale.com/tailcfg"
)
func (s *Service) GetDefaultDERPMap(ctx context.Context, _ *connect.Request[api.GetDefaultDERPMapRequest]) (*connect.Response[api.GetDefaultDERPMapResponse], error) {
@@ -17,10 +15,7 @@ func (s *Service) GetDefaultDERPMap(ctx context.Context, _ *connect.Request[api.
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
}
dm, err := s.repository.GetDERPMap(ctx)
if err != nil {
return nil, logError(err)
}
dm := domain.GetDefaultDERPMap()
raw, err := json.Marshal(dm.DERPMap)
if err != nil {
@@ -29,59 +24,3 @@ func (s *Service) GetDefaultDERPMap(ctx context.Context, _ *connect.Request[api.
return connect.NewResponse(&api.GetDefaultDERPMapResponse{Value: raw}), nil
}
func (s *Service) SetDefaultDERPMap(ctx context.Context, req *connect.Request[api.SetDefaultDERPMapRequest]) (*connect.Response[api.SetDefaultDERPMapResponse], error) {
principal := CurrentPrincipal(ctx)
if !principal.IsSystemAdmin() {
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
}
var derpMap tailcfg.DERPMap
if err := json.Unmarshal(req.Msg.Value, &derpMap); err != nil {
return nil, logError(err)
}
dp := domain.DERPMap{
Checksum: util.Checksum(&derpMap),
DERPMap: derpMap,
}
if err := s.repository.SetDERPMap(ctx, &dp); err != nil {
return nil, logError(err)
}
tailnets, err := s.repository.ListTailnets(ctx)
if err != nil {
return nil, logError(err)
}
for _, t := range tailnets {
s.sessionManager.NotifyAll(t.ID)
}
return connect.NewResponse(&api.SetDefaultDERPMapResponse{Value: req.Msg.Value}), nil
}
func (s *Service) ResetDefaultDERPMap(ctx context.Context, req *connect.Request[api.ResetDefaultDERPMapRequest]) (*connect.Response[api.ResetDefaultDERPMapResponse], error) {
principal := CurrentPrincipal(ctx)
if !principal.IsSystemAdmin() {
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
}
dp := domain.DERPMap{}
if err := s.repository.SetDERPMap(ctx, &dp); err != nil {
return nil, logError(err)
}
tailnets, err := s.repository.ListTailnets(ctx)
if err != nil {
return nil, logError(err)
}
for _, t := range tailnets {
s.sessionManager.NotifyAll(t.ID)
}
return connect.NewResponse(&api.ResetDefaultDERPMapResponse{}), nil
}
+1 -1
View File
@@ -335,7 +335,7 @@ func (s *Service) GetDERPMap(ctx context.Context, req *connect.Request[api.GetDE
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("tailnet not found"))
}
derpMap, err := tailnet.GetDERPMap(ctx, s.repository)
derpMap, err := tailnet.GetDERPMap(ctx, domain.GetDefaultDERPMap())
if err != nil {
return nil, logError(err)
}
+95
View File
@@ -0,0 +1,95 @@
package stunserver
import (
"errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"io"
"net"
"net/netip"
"time"
"tailscale.com/net/stun"
)
var (
stunRequests = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "ionscale",
Name: "stun_requests",
}, []string{"disposition"})
stunAddrFamily = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "ionscale",
Name: "stun_addr_family",
}, []string{"family"})
stunReadError = stunRequests.WithLabelValues("read_error")
stunNotSTUN = stunRequests.WithLabelValues("not_stun")
stunWriteError = stunRequests.WithLabelValues("write_error")
stunSuccess = stunRequests.WithLabelValues("success")
stunIPv4 = stunAddrFamily.WithLabelValues("ipv4")
stunIPv6 = stunAddrFamily.WithLabelValues("ipv6")
)
type STUNServer struct {
pc *net.UDPConn
}
func New(pc *net.UDPConn) *STUNServer {
return &STUNServer{pc: pc}
}
func (s *STUNServer) Serve() error {
if s.pc == nil {
return nil
}
var buf [64 << 10]byte
var (
n int
ua *net.UDPAddr
err error
)
for {
n, ua, err = s.pc.ReadFromUDP(buf[:])
if err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
return nil
}
time.Sleep(time.Second)
stunReadError.Inc()
continue
}
pkt := buf[:n]
if !stun.Is(pkt) {
stunNotSTUN.Inc()
continue
}
txid, err := stun.ParseBindingRequest(pkt)
if err != nil {
stunNotSTUN.Inc()
continue
}
if ua.IP.To4() != nil {
stunIPv4.Inc()
} else {
stunIPv6.Inc()
}
addr, _ := netip.AddrFromSlice(ua.IP)
res := stun.Response(txid, netip.AddrPortFrom(addr, uint16(ua.Port)))
_, err = s.pc.WriteTo(res, ua)
if err != nil {
stunWriteError.Inc()
} else {
stunSuccess.Inc()
}
}
}
func (s *STUNServer) Shutdown() error {
if s.pc == nil {
return nil
}
return s.pc.Close()
}