2020-02-18 19:07:44 +00:00
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The derper binary is a simple DERP server.
package main // import "tailscale.com/cmd/derper"
import (
2020-03-02 16:55:44 +00:00
"context"
2020-08-18 22:32:32 +00:00
"crypto/tls"
2020-02-18 19:07:44 +00:00
"encoding/json"
2020-03-02 16:55:44 +00:00
"errors"
2020-02-21 17:35:53 +00:00
"expvar"
2020-02-18 19:07:44 +00:00
"flag"
2021-12-06 08:28:19 +00:00
"fmt"
2020-02-18 19:07:44 +00:00
"io"
"io/ioutil"
"log"
2020-02-27 03:11:14 +00:00
"net"
2020-02-18 19:07:44 +00:00
"net/http"
"os"
"path/filepath"
2020-03-02 16:55:44 +00:00
"regexp"
2020-06-01 22:19:41 +00:00
"strings"
2020-02-27 03:11:14 +00:00
"time"
2020-02-18 19:07:44 +00:00
2022-02-03 21:59:16 +00:00
"golang.org/x/crypto/acme/autocert"
2020-02-18 19:07:44 +00:00
"tailscale.com/atomicfile"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
2020-02-21 15:52:40 +00:00
"tailscale.com/logpolicy"
2020-03-04 20:24:07 +00:00
"tailscale.com/metrics"
2020-05-25 16:15:50 +00:00
"tailscale.com/net/stun"
2020-02-25 16:07:41 +00:00
"tailscale.com/tsweb"
2020-02-18 19:07:44 +00:00
"tailscale.com/types/key"
2022-02-03 21:59:16 +00:00
"tailscale.com/types/logger"
2020-02-18 19:07:44 +00:00
)
var (
2020-02-21 17:35:53 +00:00
dev = flag . Bool ( "dev" , false , "run in localhost development mode" )
2022-02-01 01:35:53 +00:00
addr = flag . String ( "a" , ":443" , "server HTTPS listen address, in form \":port\", \"ip:port\", or for IPv6 \"[ip]:port\". If the IP is omitted, it defaults to all interfaces." )
2021-12-06 08:28:19 +00:00
httpPort = flag . Int ( "http-port" , 80 , "The port on which to serve HTTP. Set to -1 to disable" )
2020-02-21 15:52:40 +00:00
configPath = flag . String ( "c" , "" , "config file path" )
2021-09-08 23:50:34 +00:00
certMode = flag . String ( "certmode" , "letsencrypt" , "mode for getting a cert. possible options: manual, letsencrypt" )
2020-02-25 16:07:41 +00:00
certDir = flag . String ( "certdir" , tsweb . DefaultCertDir ( "derper-certs" ) , "directory to store LetsEncrypt certs, if addr's port is :443" )
2020-02-21 15:52:40 +00:00
hostname = flag . String ( "hostname" , "derp.tailscale.com" , "LetsEncrypt host name, if addr's port is :443" )
logCollection = flag . String ( "logcollection" , "" , "If non-empty, logtail collection to log to" )
2022-02-01 01:35:53 +00:00
runSTUN = flag . Bool ( "stun" , true , "whether to run a STUN server. It will bind to the same IP (if any) as the --addr flag value." )
2020-06-01 22:19:41 +00:00
meshPSKFile = flag . String ( "mesh-psk-file" , defaultMeshPSKFile ( ) , "if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed." )
2020-06-04 15:26:05 +00:00
meshWith = flag . String ( "mesh-with" , "" , "optional comma-separated list of hostnames to mesh with; the server's own hostname can be in the list" )
2021-02-26 16:28:31 +00:00
bootstrapDNS = flag . String ( "bootstrap-dns-names" , "" , "optional comma-separated list of hostnames to make available at /bootstrap-dns" )
2021-06-24 20:31:05 +00:00
verifyClients = flag . Bool ( "verify-clients" , false , "verify clients to this DERP server through a local tailscaled instance." )
2020-02-18 19:07:44 +00:00
)
2021-09-02 17:16:31 +00:00
var (
2022-01-21 22:07:40 +00:00
stats = new ( metrics . Set )
stunDisposition = & metrics . LabelMap { Label : "disposition" }
stunAddrFamily = & metrics . LabelMap { Label : "family" }
tlsRequestVersion = & metrics . LabelMap { Label : "version" }
tlsActiveVersion = & metrics . LabelMap { Label : "version" }
2021-09-02 17:16:31 +00:00
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 )
2022-01-25 18:43:47 +00:00
expvar . Publish ( "derper_tls_request_version" , tlsRequestVersion )
expvar . Publish ( "gauge_derper_tls_active_version" , tlsActiveVersion )
2021-09-02 17:16:31 +00:00
}
2020-02-18 19:07:44 +00:00
type config struct {
2021-10-28 00:29:43 +00:00
PrivateKey key . NodePrivate
2020-02-18 19:07:44 +00:00
}
func loadConfig ( ) config {
2020-02-21 17:35:53 +00:00
if * dev {
2021-10-28 00:29:43 +00:00
return config { PrivateKey : key . NewNode ( ) }
2020-02-21 17:35:53 +00:00
}
2020-02-18 19:07:44 +00:00
if * configPath == "" {
2021-07-14 22:20:38 +00:00
if os . Getuid ( ) == 0 {
* configPath = "/var/lib/derper/derper.key"
} else {
2021-07-15 00:29:06 +00:00
log . Fatalf ( "derper: -c <config path> not specified" )
2021-07-14 22:20:38 +00:00
}
log . Printf ( "no config path specified; using %s" , * configPath )
2020-02-18 19:07:44 +00:00
}
b , err := ioutil . ReadFile ( * configPath )
switch {
2020-11-02 16:33:34 +00:00
case errors . Is ( err , os . ErrNotExist ) :
2020-02-18 19:07:44 +00:00
return writeNewConfig ( )
case err != nil :
log . Fatal ( err )
panic ( "unreachable" )
default :
var cfg config
if err := json . Unmarshal ( b , & cfg ) ; err != nil {
log . Fatalf ( "derper: config: %v" , err )
}
return cfg
}
}
2020-02-21 17:35:53 +00:00
func writeNewConfig ( ) config {
2021-10-28 00:29:43 +00:00
k := key . NewNode ( )
2020-02-18 19:07:44 +00:00
if err := os . MkdirAll ( filepath . Dir ( * configPath ) , 0777 ) ; err != nil {
log . Fatal ( err )
}
cfg := config {
2021-10-28 00:29:43 +00:00
PrivateKey : k ,
2020-02-18 19:07:44 +00:00
}
b , err := json . MarshalIndent ( cfg , "" , "\t" )
if err != nil {
log . Fatal ( err )
}
2021-01-12 03:16:14 +00:00
if err := atomicfile . WriteFile ( * configPath , b , 0600 ) ; err != nil {
2020-02-18 19:07:44 +00:00
log . Fatal ( err )
}
return cfg
}
func main ( ) {
flag . Parse ( )
2020-02-21 17:35:53 +00:00
if * dev {
* logCollection = ""
* addr = ":3340" // above the keys DERP
log . Printf ( "Running in dev mode." )
2020-03-03 19:33:22 +00:00
tsweb . DevMode = true
2020-02-21 17:35:53 +00:00
}
2022-02-03 21:59:16 +00:00
if * certDir == "" {
log . Fatal ( "missing required --certdir flag" )
}
switch * certMode {
case "letsencrypt" , "manual" :
default :
log . Fatalf ( "unknown --certmode %q" , * certMode )
2021-09-02 17:42:27 +00:00
}
2020-02-21 15:52:40 +00:00
var logPol * logpolicy . Policy
if * logCollection != "" {
logPol = logpolicy . New ( * logCollection )
log . SetOutput ( logPol . Logtail )
}
2020-02-18 19:07:44 +00:00
cfg := loadConfig ( )
2022-02-03 21:59:16 +00:00
s , err := startDerper ( log . Printf , cfg . PrivateKey , * verifyClients , * meshPSKFile )
if err != nil {
log . Fatal ( err )
2020-06-03 21:42:20 +00:00
}
2020-02-21 17:35:53 +00:00
expvar . Publish ( "derp" , s . ExpVar ( ) )
2020-02-18 19:07:44 +00:00
2021-06-16 06:38:19 +00:00
mux := http . NewServeMux ( )
2022-02-03 21:59:16 +00:00
mux . Handle ( "/derp" , addWebSocketSupport ( s , derphttp . Handler ( s ) ) )
2021-10-27 16:37:32 +00:00
mux . HandleFunc ( "/derp/probe" , probeHandler )
2021-02-26 16:28:31 +00:00
mux . HandleFunc ( "/bootstrap-dns" , handleBootstrapDNS )
2020-02-18 19:07:44 +00:00
mux . Handle ( "/" , http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2020-02-21 17:35:53 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
2020-02-18 19:07:44 +00:00
w . WriteHeader ( 200 )
2020-02-21 17:35:53 +00:00
io . WriteString ( w , ` < html > < body >
< h1 > DERP < / h1 >
< p >
This is a
< a href = "https://tailscale.com/" > Tailscale < / a >
2021-02-26 16:14:13 +00:00
< a href = "https://pkg.go.dev/tailscale.com/derp" > DERP < / a >
2020-02-21 17:35:53 +00:00
server .
< / p >
` )
2020-02-25 16:07:41 +00:00
if tsweb . AllowDebugAccess ( r ) {
2020-02-21 17:35:53 +00:00
io . WriteString ( w , "<p>Debug info at <a href='/debug/'>/debug/</a>.</p>\n" )
}
2020-02-18 19:07:44 +00:00
} ) )
2022-02-03 21:59:16 +00:00
httpCfg := tsweb . ServerConfig {
Name : "derper" ,
Addr : * addr ,
Handler : mux ,
AllowedHostnames : autocertPolicy ( * hostname , * certMode == "letsencrypt" ) ,
ForceTLS : * certMode == "manual" ,
}
server := tsweb . NewServer ( httpCfg )
if server . HTTPS == nil {
log . Fatal ( "derper can only serve over TLS" )
}
server . Debug . KV ( "TLS hostname" , * hostname )
server . Debug . KV ( "Mesh key" , s . HasMeshKey ( ) )
server . Debug . Handle ( "check" , "Consistency check" , http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2021-06-16 06:38:19 +00:00
err := s . ConsistencyCheck ( )
if err != nil {
http . Error ( w , err . Error ( ) , 500 )
} else {
io . WriteString ( w , "derp.Server ConsistencyCheck okay" )
}
} ) )
2022-02-03 21:59:16 +00:00
server . Debug . Handle ( "traffic" , "Traffic check" , http . HandlerFunc ( s . ServeDebugTraffic ) )
if server . CertManager != nil {
if * hostname != "derp.tailscale.com" {
server . CertManager . Email = ""
}
// TODO: derper could just use ~/.cache/tailscale/derper, but
// for legacy compat, force the use of certDir.
server . CertManager . Cache = autocert . DirCache ( * certDir )
}
if * certMode == "manual" {
certManager , err := NewManualCertManager ( * certDir , * hostname )
if err != nil {
log . Fatalf ( "creating manual cert manager: %v" , err )
}
server . HTTPS . TLSConfig . GetCertificate = certManager . GetCertificate
}
// Append the derper meta-certificate to the "regular" TLS
// certificate chain, to enable RTT-reduced handshaking.
getCert := server . HTTPS . TLSConfig . GetCertificate
server . HTTPS . TLSConfig . GetCertificate = func ( hi * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
cert , err := getCert ( hi )
if err != nil {
return nil , err
}
cert . Certificate = append ( cert . Certificate , s . MetaCert ( ) )
return cert , nil
}
2020-02-18 19:07:44 +00:00
2020-02-27 03:11:14 +00:00
if * runSTUN {
2022-02-03 21:59:16 +00:00
listenHost , _ , err := net . SplitHostPort ( * addr )
if err != nil {
log . Fatalf ( "invalid server address: %v" , err )
}
2021-09-02 17:42:27 +00:00
go serveSTUN ( listenHost )
2020-02-27 03:11:14 +00:00
}
2022-02-03 21:59:16 +00:00
if err := server . ListenAndServe ( ) ; err != nil {
log . Fatalf ( "derper: %v" , err )
2020-02-18 19:07:44 +00:00
}
2022-02-03 21:59:16 +00:00
}
func startDerper ( logf logger . Logf , privateKey key . NodePrivate , verifyClients bool , meshPSKFile string ) ( * derp . Server , error ) {
s := derp . NewServer ( privateKey , logf )
s . SetVerifyClient ( verifyClients )
2020-02-18 19:07:44 +00:00
2022-02-03 21:59:16 +00:00
if meshPSKFile != "" {
b , err := ioutil . ReadFile ( meshPSKFile )
2021-09-08 23:50:34 +00:00
if err != nil {
2022-02-03 21:59:16 +00:00
return nil , fmt . Errorf ( "reading mesh PSK file: %v" , err )
2020-03-02 16:55:44 +00:00
}
2022-02-03 21:59:16 +00:00
key := strings . TrimSpace ( string ( b ) )
if matched , _ := regexp . MatchString ( ` (?i)^[0-9a-f] { 64,}$ ` , key ) ; ! matched {
return nil , fmt . Errorf ( "key in %s must contain 64+ hex digits" , meshPSKFile )
2021-12-06 08:28:19 +00:00
}
2022-02-03 21:59:16 +00:00
s . SetMeshKey ( key )
2020-02-18 19:07:44 +00:00
}
2022-02-03 21:59:16 +00:00
if err := startMesh ( s ) ; err != nil {
return nil , fmt . Errorf ( "startMesh: %v" , err )
2020-02-18 19:07:44 +00:00
}
2022-02-03 21:59:16 +00:00
go refreshBootstrapDNSLoop ( )
return s , nil
2020-02-18 19:07:44 +00:00
}
2020-02-21 17:35:53 +00:00
2021-10-27 16:37:32 +00:00
// probeHandler is the endpoint that js/wasm clients hit to measure
// DERP latency, since they can't do UDP STUN queries.
func probeHandler ( w http . ResponseWriter , r * http . Request ) {
switch r . Method {
case "HEAD" , "GET" :
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , "*" )
default :
http . Error ( w , "bogus probe method" , http . StatusMethodNotAllowed )
}
}
2021-09-02 17:42:27 +00:00
2021-10-27 16:37:32 +00:00
func serveSTUN ( host string ) {
2021-09-02 17:42:27 +00:00
pc , err := net . ListenPacket ( "udp" , net . JoinHostPort ( host , "3478" ) )
2020-02-27 03:11:14 +00:00
if err != nil {
log . Fatalf ( "failed to open STUN listener: %v" , err )
}
log . Printf ( "running STUN server on %v" , pc . LocalAddr ( ) )
2021-09-02 17:16:31 +00:00
serverSTUNListener ( context . Background ( ) , pc . ( * net . UDPConn ) )
}
2020-03-04 20:24:07 +00:00
2021-09-02 17:16:31 +00:00
func serverSTUNListener ( ctx context . Context , pc * net . UDPConn ) {
var buf [ 64 << 10 ] byte
2020-02-27 03:11:14 +00:00
var (
2021-09-02 17:16:31 +00:00
n int
ua * net . UDPAddr
err error
2020-02-27 03:11:14 +00:00
)
for {
2021-09-02 17:16:31 +00:00
n , ua , err = pc . ReadFromUDP ( buf [ : ] )
2020-02-27 03:11:14 +00:00
if err != nil {
2021-09-02 17:16:31 +00:00
if ctx . Err ( ) != nil {
return
}
2020-02-27 03:11:14 +00:00
log . Printf ( "STUN ReadFrom: %v" , err )
time . Sleep ( time . Second )
2020-03-04 20:24:07 +00:00
stunReadError . Add ( 1 )
2020-02-27 03:11:14 +00:00
continue
}
pkt := buf [ : n ]
if ! stun . Is ( pkt ) {
2020-03-04 20:24:07 +00:00
stunNotSTUN . Add ( 1 )
2020-02-27 03:11:14 +00:00
continue
}
txid , err := stun . ParseBindingRequest ( pkt )
if err != nil {
2020-03-04 20:24:07 +00:00
stunNotSTUN . Add ( 1 )
2020-02-27 03:11:14 +00:00
continue
}
if ua . IP . To4 ( ) != nil {
2020-03-04 20:24:07 +00:00
stunIPv4 . Add ( 1 )
2020-02-27 03:11:14 +00:00
} else {
2020-03-04 20:24:07 +00:00
stunIPv6 . Add ( 1 )
2020-02-27 03:11:14 +00:00
}
res := stun . Response ( txid , ua . IP , uint16 ( ua . Port ) )
2021-09-02 17:16:31 +00:00
_ , err = pc . WriteTo ( res , ua )
2020-02-27 03:11:14 +00:00
if err != nil {
2020-03-04 20:24:07 +00:00
stunWriteError . Add ( 1 )
2020-02-27 03:11:14 +00:00
} else {
2020-03-04 20:24:07 +00:00
stunSuccess . Add ( 1 )
2020-02-27 03:11:14 +00:00
}
}
}
2020-03-02 16:55:44 +00:00
2020-06-01 22:19:41 +00:00
var validProdHostname = regexp . MustCompile ( ` ^derp([^.]*)\.tailscale\.com\.?$ ` )
2020-03-02 16:55:44 +00:00
2022-02-03 21:59:16 +00:00
func autocertPolicy ( hostname string , useAutocert bool ) autocert . HostPolicy {
if ! useAutocert {
return nil
}
if hostname == "derp.tailscale.com" {
return prodAutocertHostPolicy
}
return autocert . HostWhitelist ( hostname )
}
2020-03-02 16:55:44 +00:00
func prodAutocertHostPolicy ( _ context . Context , host string ) error {
if validProdHostname . MatchString ( host ) {
return nil
}
return errors . New ( "invalid hostname" )
}
2020-06-01 22:19:41 +00:00
func defaultMeshPSKFile ( ) string {
2020-06-04 15:19:30 +00:00
try := [ ] string {
"/home/derp/keys/derp-mesh.key" ,
filepath . Join ( os . Getenv ( "HOME" ) , "keys" , "derp-mesh.key" ) ,
}
for _ , p := range try {
if _ , err := os . Stat ( p ) ; err == nil {
return p
}
2020-06-01 22:19:41 +00:00
}
return ""
}