derp, cmd/derper: add rate limiting support, add default 5Mbps limit

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/88/head
Brad Fitzpatrick 5 years ago committed by Dave Anderson
parent 1166c34f6c
commit 88f1cc0c98

@ -25,10 +25,11 @@ import (
) )
var ( var (
addr = flag.String("a", ":443", "server address") addr = flag.String("a", ":443", "server address")
configPath = flag.String("c", "", "config file path") configPath = flag.String("c", "", "config file path")
certDir = flag.String("certdir", defaultCertDir(), "directory to store LetsEncrypt certs, if addr's port is :443") certDir = flag.String("certdir", defaultCertDir(), "directory to store LetsEncrypt certs, if addr's port is :443")
hostname = flag.String("hostname", "derp.tailscale.com", "LetsEncrypt host name, if addr's port is :443") hostname = flag.String("hostname", "derp.tailscale.com", "LetsEncrypt host name, if addr's port is :443")
bytesPerSec = flag.Int("mbps", 5, "Mbps (mebibit/s) per-client rate limit; 0 means unlimited")
) )
func defaultCertDir() string { func defaultCertDir() string {
@ -96,6 +97,9 @@ func main() {
} }
s := derp.NewServer(key.Private(cfg.PrivateKey), log.Printf) s := derp.NewServer(key.Private(cfg.PrivateKey), log.Printf)
if *bytesPerSec != 0 {
s.BytesPerSecond = (*bytesPerSec << 20) / 8
}
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/derp", derphttp.Handler(s)) mux.Handle("/derp", derphttp.Handler(s))

@ -27,10 +27,11 @@ import (
const magic = "DERP🔑" // 8 bytes: 0x44 45 52 50 f0 9f 94 91 const magic = "DERP🔑" // 8 bytes: 0x44 45 52 50 f0 9f 94 91
const ( const (
nonceLen = 24 nonceLen = 24
keyLen = 32 keyLen = 32
maxInfoLen = 1 << 20 maxInfoLen = 1 << 20
keepAlive = 60 * time.Second keepAlive = 60 * time.Second
maxPacketData = 64 << 10
) )
// frameType is the one byte frame type at the beginning of the frame // frameType is the one byte frame type at the beginning of the frame

@ -131,7 +131,7 @@ func (c *Client) send(dstKey key.Public, pkt []byte) (ret error) {
} }
}() }()
if len(pkt) > 64<<10 { if len(pkt) > maxPacketData {
return fmt.Errorf("packet too big: %d", len(pkt)) return fmt.Errorf("packet too big: %d", len(pkt))
} }

@ -21,12 +21,17 @@ import (
"time" "time"
"golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/box"
"golang.org/x/time/rate"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
// Server is a DERP server. // Server is a DERP server.
type Server struct { type Server struct {
// BytesPerSecond, if non-zero, specifies how many bytes per
// second to cap per-client reads at.
BytesPerSecond int
privateKey key.Private privateKey key.Private
publicKey key.Public publicKey key.Public
logf logger.Logf logf logger.Logf
@ -179,6 +184,13 @@ func (s *Server) accept(nc net.Conn, brw *bufio.ReadWriter) error {
defer cancel() defer cancel()
go s.sendClientKeepAlives(ctx, c) go s.sendClientKeepAlives(ctx, c)
lim := rate.Inf
if s.BytesPerSecond != 0 {
lim = rate.Limit(s.BytesPerSecond)
}
const burstBytes = 1 << 20 // generous bandwidth delay product? must be over 64k max packet size.
limiter := rate.NewLimiter(lim, burstBytes)
for { for {
ft, fl, err := readFrameHeader(c.br) ft, fl, err := readFrameHeader(c.br)
if err != nil { if err != nil {
@ -188,8 +200,7 @@ func (s *Server) accept(nc net.Conn, brw *bufio.ReadWriter) error {
// TODO: nothing else yet supported // TODO: nothing else yet supported
return fmt.Errorf("client %x: unsupported frame %v", c.key, ft) return fmt.Errorf("client %x: unsupported frame %v", c.key, ft)
} }
dstKey, contents, err := s.recvPacket(ctx, c.br, fl, limiter)
dstKey, contents, err := s.recvPacket(c.br, fl)
if err != nil { if err != nil {
return fmt.Errorf("client %x: recvPacket: %v", c.key, err) return fmt.Errorf("client %x: recvPacket: %v", c.key, err)
} }
@ -229,6 +240,7 @@ func (s *Server) sendClientKeepAlives(ctx context.Context, c *sclient) {
func (s *Server) verifyClient(clientKey key.Public, info *sclientInfo) error { func (s *Server) verifyClient(clientKey key.Public, info *sclientInfo) error {
// TODO(crawshaw): implement policy constraints on who can use the DERP server // TODO(crawshaw): implement policy constraints on who can use the DERP server
// TODO(bradfitz): ... and at what rate.
return nil return nil
} }
@ -308,7 +320,7 @@ func (s *Server) sendPacket(bw *bufio.Writer, srcKey key.Public, contents []byte
return bw.Flush() return bw.Flush()
} }
func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Public, contents []byte, err error) { func (s *Server) recvPacket(ctx context.Context, br *bufio.Reader, frameLen uint32, limiter *rate.Limiter) (dstKey key.Public, contents []byte, err error) {
if frameLen < keyLen { if frameLen < keyLen {
return key.Public{}, nil, errors.New("short send packet frame") return key.Public{}, nil, errors.New("short send packet frame")
} }
@ -316,6 +328,12 @@ func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Publi
return key.Public{}, nil, err return key.Public{}, nil, err
} }
packetLen := frameLen - keyLen packetLen := frameLen - keyLen
if packetLen > maxPacketData {
return key.Public{}, nil, fmt.Errorf("data packet longer (%d) than max of %v", packetLen, maxPacketData)
}
if err := limiter.WaitN(ctx, int(packetLen)); err != nil {
return key.Public{}, nil, fmt.Errorf("rate limit: %v", err)
}
contents = make([]byte, packetLen) contents = make([]byte, packetLen)
if _, err := io.ReadFull(br, contents); err != nil { if _, err := io.ReadFull(br, contents); err != nil {
return key.Public{}, nil, err return key.Public{}, nil, err

@ -20,6 +20,7 @@ require (
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678 golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200217220822-9197077df867 golang.org/x/sys v0.0.0-20200217220822-9197077df867
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
gortc.io/stun v1.22.1 gortc.io/stun v1.22.1
honnef.co/go/tools v0.0.1-2019.2.3 // indirect honnef.co/go/tools v0.0.1-2019.2.3 // indirect
rsc.io/goversion v1.2.0 rsc.io/goversion v1.2.0

@ -99,6 +99,8 @@ golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=

Loading…
Cancel
Save