Files
mediamtx/internal/playback/server.go
T
Alessandro RosandGitHub 58c6099e50 ask for credentials only in case of protocols that support it (#5966)
When clients connect with some protocols (SRT, RTMP), they are unable
to provide credentials even if they are asked to. In this case, it's
useless to wait for credentials, and it's better to immediately log
authentication errors and apply the anti-brute force algorithm.
2026-07-19 16:52:14 +02:00

163 lines
4.1 KiB
Go

// Package playback contains the playback server.
package playback
import (
"fmt"
"net"
"net/http"
"sync"
"time"
"github.com/bluenviron/mediamtx/internal/auth"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/protocols/httpp"
"github.com/gin-gonic/gin"
)
type serverAuthManager interface {
Authenticate(req *auth.Request) (string, *auth.Error)
}
// Server is the playback server.
type Server struct {
Address string
DumpPackets bool
Encryption bool
ServerKey string
ServerCert string
AllowOrigins []string
TrustedProxies conf.IPNetworks
ReadTimeout conf.Duration
WriteTimeout conf.Duration
PathConfs map[string]*conf.Path
AuthManager serverAuthManager
Parent logger.Writer
httpServer *httpp.Server
mutex sync.RWMutex
}
// Initialize initializes Server.
func (s *Server) Initialize() error {
router := gin.New()
router.SetTrustedProxies(s.TrustedProxies.ToTrustedProxies()) //nolint:errcheck
router.Use(s.middlewarePreflightRequests)
router.GET("/list", s.onList)
router.GET("/get", s.onGet)
s.httpServer = &httpp.Server{
Address: s.Address,
AllowOrigins: s.AllowOrigins,
DumpPackets: s.DumpPackets,
DumpPacketsPrefix: "playback_server_conn",
ReadTimeout: time.Duration(s.ReadTimeout),
WriteTimeout: time.Duration(s.WriteTimeout),
Encryption: s.Encryption,
ServerCert: s.ServerCert,
ServerKey: s.ServerKey,
Handler: router,
Parent: s,
}
err := s.httpServer.Initialize()
if err != nil {
return err
}
str := "started with listener on " + s.Address
if !s.Encryption {
str += " (TCP/HTTP)"
} else {
str += " (TCP/HTTPS)"
}
s.Log(logger.Info, str)
return nil
}
// Close closes Server.
func (s *Server) Close() {
s.Log(logger.Info, "closing")
s.httpServer.Close()
}
// Log implements logger.Writer.
func (s *Server) Log(level logger.Level, format string, args ...any) {
s.Parent.Log(level, "[playback] "+format, args...)
}
// ReloadPathConfs is called by core.Core.
func (s *Server) ReloadPathConfs(pathConfs map[string]*conf.Path) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.PathConfs = pathConfs
}
func (s *Server) writeError(ctx *gin.Context, status int, err error) {
// show error in logs
s.Log(logger.Error, err.Error())
// add error to response
ctx.AbortWithStatusJSON(status, &defs.APIError{
Status: defs.APIErrorStatusError,
Error: err.Error(),
})
}
func (s *Server) writeErrorNoLog(ctx *gin.Context, status int, err error) {
ctx.AbortWithStatusJSON(status, &defs.APIError{
Status: defs.APIErrorStatusError,
Error: err.Error(),
})
}
func (s *Server) safeFindPathConf(name string) (*conf.Path, error) {
s.mutex.RLock()
defer s.mutex.RUnlock()
pathConf, _, err := conf.FindPathConf(s.PathConfs, name)
return pathConf, err
}
func (s *Server) middlewarePreflightRequests(ctx *gin.Context) {
if ctx.Request.Method == http.MethodOptions &&
ctx.Request.Header.Get("Access-Control-Request-Method") != "" {
ctx.Header("Access-Control-Allow-Methods", "OPTIONS, GET")
ctx.Header("Access-Control-Allow-Headers", "Authorization")
ctx.AbortWithStatus(http.StatusNoContent)
return
}
}
func (s *Server) doAuth(ctx *gin.Context, pathName string) bool {
req := &auth.Request{
Action: conf.AuthActionPlayback,
Path: pathName,
Query: ctx.Request.URL.RawQuery,
Credentials: httpp.Credentials(ctx.Request),
IP: net.ParseIP(ctx.ClientIP()),
EnableAskCredentials: true,
}
_, err := s.AuthManager.Authenticate(req)
if err != nil {
if err.AskCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return false
}
auth.LogAndDelayError(&logger.InlineWriter{
Parent: s,
Prefix: fmt.Sprintf("[conn %v]", httpp.RemoteAddr(ctx)),
}, err)
s.writeErrorNoLog(ctx, http.StatusUnauthorized, fmt.Errorf("authentication error"))
return false
}
return true
}