diff --git a/config/config.go b/config/config.go index b27a3636..8c588e6c 100644 --- a/config/config.go +++ b/config/config.go @@ -2,11 +2,13 @@ package config import ( "crypto/rand" + "errors" "fmt" "net" "os" "path/filepath" "regexp" + "strconv" "strings" "github.com/joho/godotenv" @@ -44,6 +46,7 @@ type Config struct { TurnAddress string `default:"0.0.0.0:3478" required:"true" split_words:"true"` TurnStrictAuth bool `default:"true" split_words:"true"` + TurnPortRange string `split_words:"true"` TrustProxyHeaders bool `split_words:"true"` AuthMode string `default:"turn" split_words:"true"` @@ -54,6 +57,34 @@ type Config struct { CheckOrigin func(string) bool `ignored:"true" json:"-"` } +func (c Config) parsePortRange() (uint16, uint16, error) { + if c.TurnPortRange == "" { + return 0, 0, nil + } + + parts := strings.Split(c.TurnPortRange, ":") + if len(parts) != 2 { + return 0, 0, errors.New("must include one colon") + } + stringMin := parts[0] + stringMax := parts[1] + min64, err := strconv.ParseUint(stringMin, 10, 16) + if err != nil { + return 0, 0, fmt.Errorf("invalid min: %s", err) + } + max64, err := strconv.ParseUint(stringMax, 10, 16) + if err != nil { + return 0, 0, fmt.Errorf("invalid max: %s", err) + } + + return uint16(min64), uint16(max64), nil +} + +func (c Config) PortRange() (uint16, uint16, bool) { + min, max, _ := c.parsePortRange() + return min, max, min != 0 && max != 0 +} + // Get loads the application config. func Get() (Config, []FutureLog) { var logs []FutureLog @@ -143,6 +174,21 @@ func Get() (Config, []FutureLog) { logs = append(logs, futureFatal(fmt.Sprintf("invalid SCREEGO_EXTERNAL_IP: %s", config.ExternalIP))) } + min, max, err := config.parsePortRange() + if err != nil { + logs = append(logs, futureFatal(fmt.Sprintf("invalid SCREEGO_TURN_PORT_RANGE: %s", err))) + } else if min == 0 && max == 0 { + // valid; no port range + } else if min == 0 || max == 0 { + logs = append(logs, futureFatal("invalid SCREEGO_TURN_PORT_RANGE: min or max port is 0")) + } else if min > max { + logs = append(logs, futureFatal(fmt.Sprintf("invalid SCREEGO_TURN_PORT_RANGE: min port (%d) is higher than max port (%d)", min, max))) + } else if (max - min) < 40 { + logs = append(logs, FutureLog{ + Level: zerolog.WarnLevel, + Msg: "Less than 40 ports are available for turn. When using multiple TURN connections this may not be enough"}) + } + return config, logs } diff --git a/go.mod b/go.mod index 48b01488..49307b86 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,7 @@ require ( github.com/kr/pretty v0.2.0 // indirect github.com/magiconair/properties v1.8.1 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 - github.com/pion/transport v0.10.1 // indirect - github.com/pion/turn/v2 v2.0.4 + github.com/pion/turn/v2 v2.0.5 github.com/prometheus/client_golang v1.7.1 github.com/rs/xid v1.2.1 github.com/rs/zerolog v1.19.0 diff --git a/go.sum b/go.sum index a7b81d7c..34a2701f 100644 --- a/go.sum +++ b/go.sum @@ -108,13 +108,14 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoU github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= -github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM= github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= -github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk= -github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog= +github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA= +github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -158,7 +159,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= @@ -172,7 +172,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/screego.config.example b/screego.config.example index 9af9b90e..e9cec319 100644 --- a/screego.config.example +++ b/screego.config.example @@ -21,6 +21,12 @@ SCREEGO_SERVER_ADDRESS=0.0.0.0:5050 # The address the TURN server will listen on. SCREEGO_TURN_ADDRESS=0.0.0.0:3478 +# Limit the ports that TURN will use for data relaying. +# Format: min:max +# Example: +# 50000:55000 +SCREEGO_TURN_PORT_RANGE= + # If reverse proxy headers should be trusted. # Screego uses ip whitelisting for authentication # of TURN connections. When behind a proxy the ip is always the proxy server. diff --git a/turn/server.go b/turn/server.go index 2de75813..9dcc924b 100644 --- a/turn/server.go +++ b/turn/server.go @@ -26,6 +26,18 @@ type Entry struct { const Realm = "screego" +type LoggedGenerator struct { + turn.RelayAddressGenerator +} + +func (r *LoggedGenerator) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { + conn, addr, err := r.RelayAddressGenerator.AllocatePacketConn(network, requestedPort) + if err == nil { + log.Debug().Str("addr", addr.String()).Str("network", network).Msg("TURN allocated") + } + return conn, addr, err +} + func Start(conf config.Config) (*Server, error) { udpListener, err := net.ListenPacket("udp4", conf.TurnAddress) if err != nil { @@ -44,26 +56,16 @@ func Start(conf config.Config) (*Server, error) { strictIPCheck: conf.TurnStrictAuth, } + loggedGenerator := &LoggedGenerator{RelayAddressGenerator: generator(conf)} + _, err = turn.NewServer(turn.ServerConfig{ Realm: Realm, AuthHandler: svr.authenticate, ListenerConfigs: []turn.ListenerConfig{ - { - Listener: tcpListener, - RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ - RelayAddress: net.ParseIP(conf.ExternalIP), - Address: "0.0.0.0", - }, - }, + {Listener: tcpListener, RelayAddressGenerator: loggedGenerator}, }, PacketConnConfigs: []turn.PacketConnConfig{ - { - PacketConn: udpListener, - RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ - RelayAddress: net.ParseIP(conf.ExternalIP), - Address: "0.0.0.0", - }, - }, + {PacketConn: udpListener, RelayAddressGenerator: loggedGenerator}, }, }) if err != nil { @@ -74,6 +76,23 @@ func Start(conf config.Config) (*Server, error) { return svr, nil } +func generator(conf config.Config) turn.RelayAddressGenerator { + min, max, useRange := conf.PortRange() + if useRange { + log.Debug().Uint16("min", min).Uint16("max", max).Msg("Using Port Range") + return &turn.RelayAddressGeneratorPortRange{ + RelayAddress: net.ParseIP(conf.ExternalIP), + Address: "0.0.0.0", + MinPort: min, + MaxPort: max, + } + } + return &turn.RelayAddressGeneratorStatic{ + RelayAddress: net.ParseIP(conf.ExternalIP), + Address: "0.0.0.0", + } +} + func (a *Server) Allow(username, password string, addr net.IP) { a.lock.Lock() defer a.lock.Unlock()