diff --git a/README.md b/README.md
index 3e1c9410..14b98e09 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
-_MediaMTX_ is a ready-to-use and zero-dependency real-time media server and media proxy that allows to publish, read, proxy, record and playback video and audio streams. It has been conceived as a "media router" that routes media streams from one end to the other, with a focus on efficiency and portability.
+_MediaMTX_ is a ready-to-use and zero-dependency live media server and media proxy that allows to publish, read, proxy, record and playback real-time video and audio streams. It has been conceived as a "media router" that routes media streams from one end to the other, with a focus on efficiency and portability.
diff --git a/docs/1-kickoff/1-introduction.md b/docs/1-kickoff/1-introduction.md
index f31261f8..9a23b31d 100644
--- a/docs/1-kickoff/1-introduction.md
+++ b/docs/1-kickoff/1-introduction.md
@@ -2,7 +2,7 @@
Welcome to the MediaMTX documentation!
-_MediaMTX_ is a ready-to-use and zero-dependency live media server and media proxy. It has been conceived as a "media router" that routes media streams from one end to the other, with a focus on efficiency and portability.
+_MediaMTX_ is a ready-to-use and zero-dependency live media server and media proxy that allows to publish, read, proxy, record and playback real-time video and audio streams. It has been conceived as a "media router" that routes media streams from one end to the other, with a focus on efficiency and portability.
Main features:
diff --git a/docs/3-publish/01-moq-clients.md b/docs/3-publish/01-moq-clients.md
index d476fa96..7cdf3fa5 100644
--- a/docs/3-publish/01-moq-clients.md
+++ b/docs/3-publish/01-moq-clients.md
@@ -11,7 +11,7 @@ Media-over-QUIC has a wide range of features and variants, most of them in activ
- We support `draft-18` of the [main specification](https://datatracker.ietf.org/doc/html/draft-ietf-moq-transport-18).
- We only support using Media-over-QUIC through browsers and in particular through the WebTransport API. We do not support using QUIC directly.
-- We support the `PUBLISH` and `SUBSCRIBE` messages only, which are the ones meant to be used by a routing solution like _MediaMTX_.
+- We support the `PUBLISH` and `SUBSCRIBE` messages only, which are the ones meant to be used with a routing solution like _MediaMTX_.
- We use the MOQT Streaming Format (MSF) to advertise tracks, described in [this specification](https://datatracker.ietf.org/doc/html/draft-ietf-moq-msf-00).
- We use the Low Overhead Media Container (LOC) to ship frames, described in [this specification](https://datatracker.ietf.org/doc/draft-ietf-moq-loc/).
- We host web pages through a HTTP/2 listener and host the WebTransport endpoint through a HTTP/3 listener. This hybrid setup allows to use self-signed certificates, that are normally forbidden in pure HTTP/3.
@@ -23,7 +23,7 @@ There are some server requirements:
And there are some client (browser) requirements:
-- If the server certificate is self-signed, browser must support the [serverCertificatesHashes option](https://caniuse.com/mdn-api_webtransport_webtransport_options_servercertificatehashes_parameter) (all except iOS safari do).
+- If the server certificate is self-signed, browser must support the [serverCertificatesHashes option](https://caniuse.com/mdn-api_webtransport_webtransport_options_servercertificatehashes_parameter) (all except iOS Safari do).
- Browser must support [WebTransport](https://caniuse.com/webtransport) and [WebCodecs](https://caniuse.com/webcodecs) (all modern browsers do)
- When publishing tracks, the browser to support [MediaStreamTrackProcessor](https://caniuse.com/mdn-api_mediastreamtrackprocessor) (only Chrome does).
diff --git a/docs/4-read/06-hls.md b/docs/4-read/06-hls.md
index aefcc92f..fbb5e977 100644
--- a/docs/4-read/06-hls.md
+++ b/docs/4-read/06-hls.md
@@ -20,68 +20,6 @@ http://localhost:8888/mystream/index.m3u8
Some clients that can read with HLS are [FFmpeg](08-ffmpeg.md), [GStreamer](09-gstreamer.md), [VLC](10-vlc.md) and [web browsers](07-web-browsers.md).
-_MediaMTX_ supports generating HLS in several variants (including Low-Latency mode), and provides various parameters to tune HLS generation. These are listed in the [configuration file](../5-references/1-configuration-file.md):
-
-```yml
-# Allow clients to read streams with the HLS protocol.
-hls: true
-# Address of the HLS listener.
-hlsAddress: :8888
-# Enable HTTPS on the HLS server.
-# This is required for Low-Latency HLS to function correctly on Apple devices.
-hlsEncryption: false
-# Path to the server key. This is needed only when encryption is yes.
-# This can be generated with:
-# openssl genrsa -out server.key 2048
-# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
-hlsServerKey: server.key
-# Path to the server certificate.
-hlsServerCert: server.crt
-# Allowed CORS origins.
-# Supports wildcards: ['http://*.example.com']
-hlsAllowOrigins: ["*"]
-# IPs or CIDRs of proxies placed before the HLS server.
-# If the server receives a request from one of these entries, IP in logs
-# will be taken from the X-Forwarded-For header.
-hlsTrustedProxies: []
-# By default, HLS is generated only when requested by a user.
-# This option allows to generate it always, avoiding the delay between request and generation.
-hlsAlwaysRemux: false
-# Variant of the HLS protocol to use. Available options are:
-# * mpegts - uses MPEG-TS segments, for maximum compatibility.
-# * fmp4 - uses fragmented MP4 segments, more efficient.
-# * lowLatency - uses Low-Latency HLS.
-hlsVariant: lowLatency
-# Number of HLS segments to keep on the server.
-# Segments allow to seek through the stream.
-# Their number doesn't influence latency.
-hlsSegmentCount: 7
-# Minimum duration of each segment.
-# A player usually puts 3 segments in a buffer before reproducing the stream.
-# The final segment duration is also influenced by the interval between IDR frames,
-# since the server changes the duration in order to include at least one IDR frame
-# in each segment.
-hlsSegmentDuration: 1s
-# Minimum duration of each part.
-# A player usually puts 3 parts in a buffer before reproducing the stream.
-# Parts are used in Low-Latency HLS in place of segments.
-# Part duration is influenced by the distance between video/audio samples
-# and is adjusted in order to produce segments with a similar duration.
-hlsPartDuration: 200ms
-# Maximum size of each segment.
-# This prevents RAM exhaustion.
-hlsSegmentMaxSize: 50M
-# Directory in which to save segments and non-low-latency playlists.
-# This has two purposes: offloading RAM and creating a self-consistent directory
-# that can be served by a CDN.
-hlsDirectory: ""
-# The muxer will be closed when there are no
-# reader requests and this amount of time has passed.
-hlsMuxerCloseAfter: 60s
-# Secret to identify requests coming from a CDN.
-# The CDN must insert this secret in every request in the
-# 'Authorization: Bearer' header.
-hlsCDNSecret: ""
-```
+_MediaMTX_ supports generating HLS in several variants (including Low-Latency mode), and provides various parameters to tune HLS generation. These are listed in the [configuration file](../5-references/1-configuration-file.md).
HLS can also be used to [scale the server](../2-features/20-scalability.md) through a CDN.
diff --git a/internal/api/api.go b/internal/api/api.go
index 5646b5ba..9ebf9518 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -257,6 +257,8 @@ func (a *API) middlewareAuth(ctx *gin.Context) {
_, err := a.AuthManager.Authenticate(req)
if err != nil {
+ auth.DelayBruteForce(err)
+
if err.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
a.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
@@ -265,9 +267,6 @@ func (a *API) middlewareAuth(ctx *gin.Context) {
a.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), err.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
a.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
diff --git a/internal/auth/delay_brute_force.go b/internal/auth/delay_brute_force.go
new file mode 100644
index 00000000..b37dd840
--- /dev/null
+++ b/internal/auth/delay_brute_force.go
@@ -0,0 +1,28 @@
+package auth
+
+import (
+ "crypto/rand"
+ "errors"
+ "math/big"
+ "time"
+)
+
+const (
+ minPause = 0 * time.Second
+ maxPause = 4 * time.Second
+)
+
+// DelayBruteForce delays brute force attacks by waiting some seconds after an authentication error.
+func DelayBruteForce(err error) {
+ if terr, ok := errors.AsType[*Error](err); ok {
+ if !terr.AskCredentials {
+ var n *big.Int
+ n, err = rand.Int(rand.Reader, big.NewInt(int64(maxPause-minPause)))
+ if err != nil {
+ <-time.After(maxPause)
+ return
+ }
+ <-time.After(minPause + time.Duration(n.Int64()))
+ }
+ }
+}
diff --git a/internal/auth/manager.go b/internal/auth/manager.go
index 0e8c75b2..f3bf922b 100644
--- a/internal/auth/manager.go
+++ b/internal/auth/manager.go
@@ -21,9 +21,6 @@ import (
)
const (
- // PauseAfterError is the pause to apply after an authentication failure.
- PauseAfterError = 2 * time.Second
-
maxInboundBodySize = 128 * 1024
jwksRefreshPeriod = 60 * 60 * time.Second
)
diff --git a/internal/core/path.go b/internal/core/path.go
index ad04c696..2ac04811 100644
--- a/internal/core/path.go
+++ b/internal/core/path.go
@@ -1040,12 +1040,14 @@ func (pa *path) StaticSourceHandlerSetNotReady(
}
// describe is called by a reader or publisher through pathManager.
-func (pa *path) describe(req defs.PathDescribeReq) defs.PathDescribeRes {
+func (pa *path) describe(req defs.PathDescribeReq) (*defs.PathDescribeRes, error) {
select {
case pa.chDescribe <- req:
- return <-req.Res
+ res := <-req.Res
+ return &res, res.Err
+
case <-pa.ctx.Done():
- return defs.PathDescribeRes{Err: fmt.Errorf("terminated")}
+ return nil, fmt.Errorf("terminated")
}
}
diff --git a/internal/core/path_manager.go b/internal/core/path_manager.go
index dbd29d5e..7614e7b0 100644
--- a/internal/core/path_manager.go
+++ b/internal/core/path_manager.go
@@ -529,7 +529,13 @@ func (pm *pathManager) FindPathConf(req defs.PathFindPathConfReq) (*defs.PathFin
select {
case pm.chFindPathConf <- req:
res := <-req.Res
- return &res, res.Err
+
+ if res.Err != nil {
+ auth.DelayBruteForce(res.Err)
+ return nil, res.Err
+ }
+
+ return &res, nil
case <-pm.ctx.Done():
return nil, fmt.Errorf("terminated")
@@ -537,25 +543,26 @@ func (pm *pathManager) FindPathConf(req defs.PathFindPathConfReq) (*defs.PathFin
}
// Describe is called by a reader or publisher.
-func (pm *pathManager) Describe(req defs.PathDescribeReq) defs.PathDescribeRes {
+func (pm *pathManager) Describe(req defs.PathDescribeReq) (*defs.PathDescribeRes, error) {
req.Res = make(chan defs.PathDescribeRes)
select {
case pm.chDescribe <- req:
res1 := <-req.Res
if res1.Err != nil {
- return res1
+ auth.DelayBruteForce(res1.Err)
+ return nil, res1.Err
}
- res2 := res1.Path.(*path).describe(req)
- if res2.Err != nil {
- return res2
+ res2, err := res1.Path.(*path).describe(req)
+ if err != nil {
+ return nil, err
}
res2.Path = res1.Path
- return res2
+ return res2, nil
case <-pm.ctx.Done():
- return defs.PathDescribeRes{Err: fmt.Errorf("terminated")}
+ return nil, fmt.Errorf("terminated")
}
}
@@ -566,6 +573,7 @@ func (pm *pathManager) AddPublisher(req defs.PathAddPublisherReq) (*defs.PathAdd
case pm.chAddPublisher <- req:
res1 := <-req.Res
if res1.Err != nil {
+ auth.DelayBruteForce(res1.Err)
return nil, res1.Err
}
@@ -591,6 +599,7 @@ func (pm *pathManager) AddReader(req defs.PathAddReaderReq) (*defs.PathAddReader
case pm.chAddReader <- req:
res1 := <-req.Res
if res1.Err != nil {
+ auth.DelayBruteForce(res1.Err)
return nil, res1.Err
}
diff --git a/internal/core/path_manager_test.go b/internal/core/path_manager_test.go
index cc1bec06..f9a18867 100644
--- a/internal/core/path_manager_test.go
+++ b/internal/core/path_manager_test.go
@@ -58,12 +58,12 @@ func TestPathManagerDynamicPathAutoDeletion(t *testing.T) {
func() {
if ca == "describe" {
- res := pm.Describe(defs.PathDescribeReq{
+ _, err := pm.Describe(defs.PathDescribeReq{
AccessRequest: defs.PathAccessRequest{
Name: "mypath",
},
})
- require.EqualError(t, res.Err, "no stream is available on path 'mypath'")
+ require.EqualError(t, err, "no stream is available on path 'mypath'")
} else {
_, err := pm.AddReader(defs.PathAddReaderReq{
Author: &dummyReader{},
@@ -104,7 +104,7 @@ func TestPathManagerDynamicPathDescribeAndPublish(t *testing.T) {
go func() {
for range 10 {
- pm.Describe(defs.PathDescribeReq{
+ pm.Describe(defs.PathDescribeReq{ //nolint:errcheck
AccessRequest: defs.PathAccessRequest{
Name: "mypath",
},
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
index ea9e44de..62e42f84 100644
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -198,6 +198,8 @@ func (m *Metrics) middlewareAuth(ctx *gin.Context) {
_, err := m.AuthManager.Authenticate(req)
if err != nil {
+ auth.DelayBruteForce(err)
+
if err.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
m.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
@@ -206,9 +208,6 @@ func (m *Metrics) middlewareAuth(ctx *gin.Context) {
m.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), err.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
m.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
diff --git a/internal/playback/server.go b/internal/playback/server.go
index 061fa051..621596e1 100644
--- a/internal/playback/server.go
+++ b/internal/playback/server.go
@@ -142,6 +142,8 @@ func (s *Server) doAuth(ctx *gin.Context, pathName string) bool {
_, err := s.AuthManager.Authenticate(req)
if err != nil {
+ auth.DelayBruteForce(err)
+
if err.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
@@ -151,9 +153,6 @@ func (s *Server) doAuth(ctx *gin.Context, pathName string) bool {
s.Log(logger.Info, "connection %v failed to authenticate: %v",
httpp.RemoteAddr(ctx), err.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return false
}
diff --git a/internal/playback/server_test.go b/internal/playback/server_test.go
index 7d719835..116b500e 100644
--- a/internal/playback/server_test.go
+++ b/internal/playback/server_test.go
@@ -107,14 +107,10 @@ func TestAuthError(t *testing.T) {
req, err = http.NewRequest(http.MethodGet, u.String(), nil)
require.NoError(t, err)
- start := time.Now()
-
res, err = http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
- require.Greater(t, time.Since(start), 2*time.Second)
-
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
require.Equal(t, 2, n)
diff --git a/internal/pprof/pprof.go b/internal/pprof/pprof.go
index 41a9c732..e353a255 100644
--- a/internal/pprof/pprof.go
+++ b/internal/pprof/pprof.go
@@ -118,6 +118,8 @@ func (pp *PPROF) middlewareAuth(ctx *gin.Context) {
_, err := pp.AuthManager.Authenticate(req)
if err != nil {
+ auth.DelayBruteForce(err)
+
if err.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
pp.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
@@ -126,9 +128,6 @@ func (pp *PPROF) middlewareAuth(ctx *gin.Context) {
pp.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), err.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
pp.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
diff --git a/internal/servers/hls/http_server.go b/internal/servers/hls/http_server.go
index e58b00a6..2ad9570a 100644
--- a/internal/servers/hls/http_server.go
+++ b/internal/servers/hls/http_server.go
@@ -224,9 +224,6 @@ func (s *httpServer) onRequest(ctx *gin.Context) {
s.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), terr.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
@@ -343,9 +340,6 @@ func (s *httpServer) onRequest(ctx *gin.Context) {
s.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), terr.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
@@ -398,9 +392,6 @@ func (s *httpServer) onRequest(ctx *gin.Context) {
create: false,
})
if err != nil {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
@@ -412,9 +403,6 @@ func (s *httpServer) onRequest(ctx *gin.Context) {
sx = muxer.findSession(ctx)
}
if sx == nil {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
diff --git a/internal/servers/hls/server_test.go b/internal/servers/hls/server_test.go
index 13d47db2..f2752dc2 100644
--- a/internal/servers/hls/server_test.go
+++ b/internal/servers/hls/server_test.go
@@ -645,14 +645,10 @@ func TestAuthError(t *testing.T) {
req, err = http.NewRequest(http.MethodGet, "http://myuser:mypass@127.0.0.1:8888/stream/index.m3u8", nil)
require.NoError(t, err)
- start := time.Now()
-
res, err = http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
- require.Greater(t, time.Since(start), 2*time.Second)
-
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
require.Equal(t, 2, n)
diff --git a/internal/servers/moq/http_server.go b/internal/servers/moq/http_server.go
index cf16eec9..862ac442 100644
--- a/internal/servers/moq/http_server.go
+++ b/internal/servers/moq/http_server.go
@@ -191,9 +191,6 @@ func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string,
s.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), terr.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return false
}
diff --git a/internal/servers/moq/session.go b/internal/servers/moq/session.go
index d54aea18..51df53d5 100644
--- a/internal/servers/moq/session.go
+++ b/internal/servers/moq/session.go
@@ -358,11 +358,6 @@ func (s *session) onSubscribeCatalog(wstream *webtransport.Stream, m *controlmes
},
})
if err != nil {
- if _, ok := errors.AsType[*auth.Error](err); ok {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
- }
-
var code controlmessage.RequestErrorCode
if _, ok := errors.AsType[*auth.Error](err); ok {
code = controlmessage.RequestErrorCodeUnauthorized
@@ -595,11 +590,6 @@ func (s *session) onPublishCatalog(wstream *webtransport.Stream, m *controlmessa
},
})
if err != nil {
- if _, ok := errors.AsType[*auth.Error](err); ok {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
- }
-
var code controlmessage.RequestErrorCode
if _, ok := errors.AsType[*auth.Error](err); ok {
code = controlmessage.RequestErrorCodeUnauthorized
diff --git a/internal/servers/rtmp/conn.go b/internal/servers/rtmp/conn.go
index 581e9b68..03466a09 100644
--- a/internal/servers/rtmp/conn.go
+++ b/internal/servers/rtmp/conn.go
@@ -171,11 +171,6 @@ func (c *conn) runRead() error {
},
})
if err != nil {
- if terr, ok := errors.AsType[*auth.Error](err); ok {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
- return terr
- }
return err
}
@@ -265,11 +260,6 @@ func (c *conn) runPublish() error {
},
})
if err != nil {
- if terr, ok := errors.AsType[*auth.Error](err); ok {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
- return terr
- }
return err
}
diff --git a/internal/servers/rtsp/conn.go b/internal/servers/rtsp/conn.go
index caae2e44..b4d047b3 100644
--- a/internal/servers/rtsp/conn.go
+++ b/internal/servers/rtsp/conn.go
@@ -151,7 +151,7 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
}
}
- res := c.pathManager.Describe(defs.PathDescribeReq{
+ res, err := c.pathManager.Describe(defs.PathDescribeReq{
AccessRequest: defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
@@ -162,22 +162,21 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
CustomVerifyFunc: customVerifyFunc,
},
})
-
- if res.Err != nil {
- if terr, ok := errors.AsType[*auth.Error](res.Err); ok {
+ if err != nil {
+ if terr, ok := errors.AsType[*auth.Error](err); ok {
res, err2 := c.handleAuthError(terr)
return res, nil, err2
}
- if _, ok := errors.AsType[*defs.PathNoStreamAvailableError](res.Err); ok {
+ if _, ok := errors.AsType[*defs.PathNoStreamAvailableError](err); ok {
return &base.Response{
StatusCode: base.StatusNotFound,
- }, nil, res.Err
+ }, nil, err
}
return &base.Response{
StatusCode: base.StatusBadRequest,
- }, nil, res.Err
+ }, nil, err
}
if res.Redirect != "" {
@@ -208,9 +207,6 @@ func (c *conn) handleAuthError(err *auth.Error) (*base.Response, error) {
}, liberrors.ErrServerAuth{}
}
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
return &base.Response{
StatusCode: base.StatusUnauthorized,
}, err
diff --git a/internal/servers/rtsp/server.go b/internal/servers/rtsp/server.go
index efb07b2e..f6f31fce 100644
--- a/internal/servers/rtsp/server.go
+++ b/internal/servers/rtsp/server.go
@@ -77,7 +77,7 @@ type serverMetrics interface {
type serverPathManager interface {
FindPathConf(req defs.PathFindPathConfReq) (*defs.PathFindPathConfRes, error)
- Describe(req defs.PathDescribeReq) defs.PathDescribeRes
+ Describe(req defs.PathDescribeReq) (*defs.PathDescribeRes, error)
AddPublisher(_ defs.PathAddPublisherReq) (*defs.PathAddPublisherRes, error)
AddReader(_ defs.PathAddReaderReq) (*defs.PathAddReaderRes, error)
}
diff --git a/internal/servers/rtsp/server_test.go b/internal/servers/rtsp/server_test.go
index f16abc4b..57fa453c 100644
--- a/internal/servers/rtsp/server_test.go
+++ b/internal/servers/rtsp/server_test.go
@@ -376,7 +376,7 @@ func TestServerRead(t *testing.T) {
n := 0
pathManager := &test.PathManager{
- DescribeImpl: func(req defs.PathDescribeReq) defs.PathDescribeRes {
+ DescribeImpl: func(req defs.PathDescribeReq) (*defs.PathDescribeRes, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
@@ -384,7 +384,7 @@ func TestServerRead(t *testing.T) {
require.Nil(t, req.AccessRequest.CustomVerifyFunc)
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
- return defs.PathDescribeRes{Err: &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}}
+ return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}
}
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
@@ -394,16 +394,15 @@ func TestServerRead(t *testing.T) {
if n == 0 {
require.False(t, ok)
n++
- return defs.PathDescribeRes{Err: &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}}
+ return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}
}
require.True(t, ok)
}
- return defs.PathDescribeRes{
+ return &defs.PathDescribeRes{
Path: &dummyPath{},
Stream: strm,
- Err: nil,
- }
+ }, nil
},
AddReaderImpl: func(req defs.PathAddReaderReq) (*defs.PathAddReaderRes, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
@@ -552,20 +551,20 @@ func TestServerRedirect(t *testing.T) {
require.NoError(t, err)
pathManager := &test.PathManager{
- DescribeImpl: func(req defs.PathDescribeReq) defs.PathDescribeRes {
+ DescribeImpl: func(req defs.PathDescribeReq) (*defs.PathDescribeRes, error) {
if req.AccessRequest.Name == "path1" {
if ca == "relative" {
- return defs.PathDescribeRes{
+ return &defs.PathDescribeRes{
Redirect: "/path2",
- }
+ }, nil
}
- return defs.PathDescribeRes{
+ return &defs.PathDescribeRes{
Redirect: "rtsp://localhost:8557/path2",
- }
+ }, nil
}
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
- return defs.PathDescribeRes{Err: &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}}
+ return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}
}
require.Equal(t, "path2", req.AccessRequest.Name)
@@ -573,10 +572,10 @@ func TestServerRedirect(t *testing.T) {
require.Equal(t, "myuser", req.AccessRequest.Credentials.User)
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
- return defs.PathDescribeRes{
+ return &defs.PathDescribeRes{
Path: &dummyPath{},
Stream: strm,
- }
+ }, nil
},
}
@@ -616,12 +615,12 @@ func TestServerRedirect(t *testing.T) {
func TestAuthError(t *testing.T) {
pathManager := &test.PathManager{
- DescribeImpl: func(req defs.PathDescribeReq) defs.PathDescribeRes {
+ DescribeImpl: func(req defs.PathDescribeReq) (*defs.PathDescribeRes, error) {
if req.AccessRequest.Credentials.User == "" && req.AccessRequest.Credentials.Pass == "" {
- return defs.PathDescribeRes{Err: &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}}
+ return nil, &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("auth error")}
}
- return defs.PathDescribeRes{Err: &auth.Error{Wrapped: fmt.Errorf("auth error")}}
+ return nil, &auth.Error{Wrapped: fmt.Errorf("auth error")}
},
}
diff --git a/internal/servers/srt/conn.go b/internal/servers/srt/conn.go
index e1348d8f..1e197d2e 100644
--- a/internal/servers/srt/conn.go
+++ b/internal/servers/srt/conn.go
@@ -149,8 +149,6 @@ func (c *conn) runPublish(streamID *streamID) error {
})
if err != nil {
if terr, ok := errors.AsType[*auth.Error](err); ok {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
c.connReq.Reject(srt.REJ_PEER)
return terr
}
@@ -275,8 +273,6 @@ func (c *conn) runRead(streamID *streamID) error {
})
if err != nil {
if terr, ok := errors.AsType[*auth.Error](err); ok {
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
c.connReq.Reject(srt.REJ_PEER)
return terr
}
diff --git a/internal/servers/webrtc/http_server.go b/internal/servers/webrtc/http_server.go
index 9fc5f27d..cab2a93d 100644
--- a/internal/servers/webrtc/http_server.go
+++ b/internal/servers/webrtc/http_server.go
@@ -158,9 +158,6 @@ func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string,
s.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), terr.Wrapped)
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return false
}
@@ -223,12 +220,6 @@ func (s *httpServer) onWHIPPost(ctx *gin.Context, pathName string, publish bool)
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
-
- s.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), terr.Wrapped)
-
- // wait some seconds to delay brute force attacks
- <-time.After(auth.PauseAfterError)
-
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return
}
diff --git a/internal/servers/webrtc/server_test.go b/internal/servers/webrtc/server_test.go
index a0e242a4..5d83c23f 100644
--- a/internal/servers/webrtc/server_test.go
+++ b/internal/servers/webrtc/server_test.go
@@ -10,6 +10,7 @@ import (
"net/url"
"reflect"
"regexp"
+ "sync/atomic"
"testing"
"time"
@@ -1160,7 +1161,7 @@ func TestAuthError(t *testing.T) {
"whip post",
} {
t.Run(ca, func(t *testing.T) {
- authFailed := false
+ var authFailed atomic.Bool
s := &Server{
Address: "127.0.0.1:8886",
@@ -1175,11 +1176,13 @@ func TestAuthError(t *testing.T) {
return nil, &auth.Error{Wrapped: fmt.Errorf("auth error")}
},
},
- Parent: test.Logger(func(l logger.Level, s string, i ...any) {
- if l == logger.Info {
- if regexp.MustCompile("failed to authenticate: auth error$").MatchString(fmt.Sprintf(s, i...)) {
- authFailed = true
+ Parent: test.Logger(func(_ logger.Level, s string, i ...any) {
+ if ca == "whip post" {
+ if regexp.MustCompile("authentication failed: auth error$").MatchString(fmt.Sprintf(s, i...)) {
+ authFailed.Store(true)
}
+ } else if regexp.MustCompile("failed to authenticate: auth error$").MatchString(fmt.Sprintf(s, i...)) {
+ authFailed.Store(true)
}
}),
}
@@ -1250,17 +1253,13 @@ func TestAuthError(t *testing.T) {
require.NoError(t, err)
- start := time.Now()
-
res, err = http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
- require.Greater(t, time.Since(start), 2*time.Second)
-
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
- require.True(t, authFailed)
+ require.True(t, authFailed.Load())
})
}
}
diff --git a/internal/test/path_manager.go b/internal/test/path_manager.go
index b4326910..f035fb3b 100644
--- a/internal/test/path_manager.go
+++ b/internal/test/path_manager.go
@@ -7,7 +7,7 @@ import (
// PathManager is a dummy path manager.
type PathManager struct {
FindPathConfImpl func(req defs.PathFindPathConfReq) (*defs.PathFindPathConfRes, error)
- DescribeImpl func(req defs.PathDescribeReq) defs.PathDescribeRes
+ DescribeImpl func(req defs.PathDescribeReq) (*defs.PathDescribeRes, error)
AddPublisherImpl func(req defs.PathAddPublisherReq) (*defs.PathAddPublisherRes, error)
AddReaderImpl func(req defs.PathAddReaderReq) (*defs.PathAddReaderRes, error)
}
@@ -18,7 +18,7 @@ func (pm *PathManager) FindPathConf(req defs.PathFindPathConfReq) (*defs.PathFin
}
// Describe implements PathManager.
-func (pm *PathManager) Describe(req defs.PathDescribeReq) defs.PathDescribeRes {
+func (pm *PathManager) Describe(req defs.PathDescribeReq) (*defs.PathDescribeRes, error) {
return pm.DescribeImpl(req)
}