diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index 62eca60c4..6a1979611 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -13,9 +13,11 @@ import ( "io" "io/ioutil" "log" + "net" "net/http" "os" "path/filepath" + "time" "github.com/tailscale/wireguard-go/wgcfg" "golang.org/x/crypto/acme/autocert" @@ -23,6 +25,7 @@ import ( "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/logpolicy" + "tailscale.com/stun" "tailscale.com/tsweb" "tailscale.com/types/key" ) @@ -35,6 +38,7 @@ var ( hostname = flag.String("hostname", "derp.tailscale.com", "LetsEncrypt host name, if addr's port is :443") mbps = flag.Int("mbps", 5, "Mbps (mebibit/s) per-client rate limit; 0 means unlimited") logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to") + runSTUN = flag.Bool("stun", false, "also run a STUN server") ) type config struct { @@ -135,6 +139,10 @@ func main() { } })) + if *runSTUN { + go serveSTUN() + } + httpsrv := &http.Server{ Addr: *addr, Handler: mux, @@ -190,3 +198,58 @@ func debugHandler(s *derp.Server) http.Handler { `) }) } + +func serveSTUN() { + pc, err := net.ListenPacket("udp", ":3478") + if err != nil { + log.Fatalf("failed to open STUN listener: %v", err) + } + log.Printf("running STUN server on %v", pc.LocalAddr()) + var ( + stunReadErrors = expvar.NewInt("stun-read-error") + stunWriteErrors = expvar.NewInt("stun-write-error") + stunReadNotSTUN = expvar.NewInt("stun-read-not-stun") + stunReadNotSTUNValid = expvar.NewInt("stun-read-not-stun-valid") + stunReadIPv4 = expvar.NewInt("stun-read-ipv4") + stunReadIPv6 = expvar.NewInt("stun-read-ipv6") + stunWrite = expvar.NewInt("stun-write") + ) + var buf [64 << 10]byte + for { + n, addr, err := pc.ReadFrom(buf[:]) + if err != nil { + log.Printf("STUN ReadFrom: %v", err) + time.Sleep(time.Second) + stunReadErrors.Add(1) + continue + } + ua, ok := addr.(*net.UDPAddr) + if !ok { + log.Printf("STUN unexpected address %T %v", addr, addr) + stunReadErrors.Add(1) + continue + } + pkt := buf[:n] + if !stun.Is(pkt) { + stunReadNotSTUN.Add(1) + continue + } + txid, err := stun.ParseBindingRequest(pkt) + if err != nil { + stunReadNotSTUNValid.Add(1) + continue + } + if ua.IP.To4() != nil { + stunReadIPv4.Add(1) + } else { + stunReadIPv6.Add(1) + } + res := stun.Response(txid, ua.IP, uint16(ua.Port)) + _, err = pc.WriteTo(res, addr) + if err != nil { + stunWriteErrors.Add(1) + } else { + stunWrite.Add(1) + } + } +}