Skip to content

Commit

Permalink
cmd/{derper,stund},net/stunserver: add standalone stun server
Browse files Browse the repository at this point in the history
Add a standalone server for STUN that can be hosted independently of the
derper, and factor that back into the derper.

Fixes tailscale#8434
Closes tailscale#8435
Closes tailscale#10745

Signed-off-by: James Tucker <james@tailscale.com>
  • Loading branch information
raggi committed Jan 9, 2024
1 parent 569b914 commit 953fa80
Show file tree
Hide file tree
Showing 8 changed files with 474 additions and 110 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ updatedeps: ## Update depaware deps
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --update \
tailscale.com/cmd/tailscaled \
tailscale.com/cmd/tailscale \
tailscale.com/cmd/derper
tailscale.com/cmd/derper \
tailscale.com/cmd/stund

depaware: ## Run depaware checks
# depaware (via x/tools/go/packages) shells back to "go", so make sure the "go"
# it finds in its $$PATH is the right one.
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --check \
tailscale.com/cmd/tailscaled \
tailscale.com/cmd/tailscale \
tailscale.com/cmd/derper
tailscale.com/cmd/derper \
tailscale.com/cmd/stund

buildwindows: ## Build tailscale CLI for windows/amd64
GOOS=windows GOARCH=amd64 ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
Expand Down
4 changes: 3 additions & 1 deletion cmd/derper/depaware.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/net/netutil from tailscale.com/client/tailscale
tailscale.com/net/packet from tailscale.com/wgengine/filter
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
tailscale.com/net/stun from tailscale.com/cmd/derper
tailscale.com/net/stun from tailscale.com/net/stunserver
tailscale.com/net/stunserver from tailscale.com/cmd/derper
L tailscale.com/net/tcpinfo from tailscale.com/derp
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
tailscale.com/net/tsaddr from tailscale.com/ipn+
Expand Down Expand Up @@ -263,6 +264,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
net/url from crypto/x509+
os from crypto/rand+
os/exec from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
os/signal from tailscale.com/cmd/derper
W os/user from tailscale.com/util/winutil
path from golang.org/x/crypto/acme/autocert+
path/filepath from crypto/x509+
Expand Down
88 changes: 15 additions & 73 deletions cmd/derper/derper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ import (
"math"
"net"
"net/http"
"net/netip"
"os"
"os/signal"
"path/filepath"
"regexp"
"strings"
"syscall"
"time"

"go4.org/mem"
Expand All @@ -30,7 +31,7 @@ import (
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/metrics"
"tailscale.com/net/stun"
"tailscale.com/net/stunserver"
"tailscale.com/tsweb"
"tailscale.com/types/key"
"tailscale.com/util/cmpx"
Expand Down Expand Up @@ -59,25 +60,11 @@ var (
)

var (
stats = new(metrics.Set)
stunDisposition = &metrics.LabelMap{Label: "disposition"}
stunAddrFamily = &metrics.LabelMap{Label: "family"}
tlsRequestVersion = &metrics.LabelMap{Label: "version"}
tlsActiveVersion = &metrics.LabelMap{Label: "version"}

stunReadError = stunDisposition.Get("read_error")
stunNotSTUN = stunDisposition.Get("not_stun")
stunWriteError = stunDisposition.Get("write_error")
stunSuccess = stunDisposition.Get("success")

stunIPv4 = stunAddrFamily.Get("ipv4")
stunIPv6 = stunAddrFamily.Get("ipv6")
)

func init() {
stats.Set("counter_requests", stunDisposition)
stats.Set("counter_addrfamily", stunAddrFamily)
expvar.Publish("stun", stats)
expvar.Publish("derper_tls_request_version", tlsRequestVersion)
expvar.Publish("gauge_derper_tls_active_version", tlsActiveVersion)
}
Expand Down Expand Up @@ -135,6 +122,9 @@ func writeNewConfig() config {
func main() {
flag.Parse()

ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()

if *dev {
*addr = ":3340" // above the keys DERP
log.Printf("Running in dev mode.")
Expand All @@ -146,6 +136,11 @@ func main() {
log.Fatalf("invalid server address: %v", err)
}

if *runSTUN {
ss := stunserver.New(ctx)
go ss.ListenAndServe(net.JoinHostPort(listenHost, fmt.Sprint(*stunPort)))
}

cfg := loadConfig()

serveTLS := tsweb.IsProd443(*addr) || *certMode == "manual"
Expand Down Expand Up @@ -221,10 +216,6 @@ func main() {
}))
debug.Handle("traffic", "Traffic check", http.HandlerFunc(s.ServeDebugTraffic))

if *runSTUN {
go serveSTUN(listenHost, *stunPort)
}

quietLogger := log.New(logFilter{}, "", 0)
httpsrv := &http.Server{
Addr: *addr,
Expand All @@ -241,6 +232,10 @@ func main() {
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
}
go func() {
<-ctx.Done()
httpsrv.Shutdown(ctx)
}()

if serveTLS {
log.Printf("derper: serving on %s with TLS", *addr)
Expand Down Expand Up @@ -351,59 +346,6 @@ func probeHandler(w http.ResponseWriter, r *http.Request) {
}
}

func serveSTUN(host string, port int) {
pc, err := net.ListenPacket("udp", net.JoinHostPort(host, fmt.Sprint(port)))
if err != nil {
log.Fatalf("failed to open STUN listener: %v", err)
}
log.Printf("running STUN server on %v", pc.LocalAddr())
serverSTUNListener(context.Background(), pc.(*net.UDPConn))
}

func serverSTUNListener(ctx context.Context, pc *net.UDPConn) {
var buf [64 << 10]byte
var (
n int
ua *net.UDPAddr
err error
)
for {
n, ua, err = pc.ReadFromUDP(buf[:])
if err != nil {
if ctx.Err() != nil {
return
}
log.Printf("STUN ReadFrom: %v", err)
time.Sleep(time.Second)
stunReadError.Add(1)
continue
}
pkt := buf[:n]
if !stun.Is(pkt) {
stunNotSTUN.Add(1)
continue
}
txid, err := stun.ParseBindingRequest(pkt)
if err != nil {
stunNotSTUN.Add(1)
continue
}
if ua.IP.To4() != nil {
stunIPv4.Add(1)
} else {
stunIPv6.Add(1)
}
addr, _ := netip.AddrFromSlice(ua.IP)
res := stun.Response(txid, netip.AddrPortFrom(addr, uint16(ua.Port)))
_, err = pc.WriteTo(res, ua)
if err != nil {
stunWriteError.Add(1)
} else {
stunSuccess.Add(1)
}
}
}

var validProdHostname = regexp.MustCompile(`^derp([^.]*)\.tailscale\.com\.?$`)

func prodAutocertHostPolicy(_ context.Context, host string) error {
Expand Down
34 changes: 0 additions & 34 deletions cmd/derper/derper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ package main

import (
"context"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"

"tailscale.com/net/stun"
"tailscale.com/tstest/deptest"
)

Expand Down Expand Up @@ -39,38 +37,6 @@ func TestProdAutocertHostPolicy(t *testing.T) {
}
}

func BenchmarkServerSTUN(b *testing.B) {
b.ReportAllocs()
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer pc.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go serverSTUNListener(ctx, pc.(*net.UDPConn))
addr := pc.LocalAddr().(*net.UDPAddr)

var resBuf [1500]byte
cc, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
if err != nil {
b.Fatal(err)
}

tx := stun.NewTxID()
req := stun.Request(tx)
for i := 0; i < b.N; i++ {
if _, err := cc.WriteToUDP(req, addr); err != nil {
b.Fatal(err)
}
_, _, err := cc.ReadFromUDP(resBuf[:])
if err != nil {
b.Fatal(err)
}
}

}

func TestNoContent(t *testing.T) {
testCases := []struct {
name string
Expand Down

0 comments on commit 953fa80

Please sign in to comment.