Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

235 lines
5.2 KiB

package core
import (
"fmt"
"net"
"time"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/externalcmd"
"github.com/bluenviron/mediamtx/internal/logger"
)
const (
rtspPauseAfterAuthError = 2 * time.Second
)
type rtspConnParent interface {
logger.Writer
getISTLS() bool
getServer() *gortsplib.Server
}
type rtspConn struct {
*conn
isTLS bool
rtspAddress string
authMethods []headers.AuthMethod
readTimeout conf.StringDuration
pathManager *pathManager
rconn *gortsplib.ServerConn
parent rtspConnParent
uuid uuid.UUID
created time.Time
authNonce string
authFailures int
}
func newRTSPConn(
isTLS bool,
rtspAddress string,
authMethods []headers.AuthMethod,
readTimeout conf.StringDuration,
runOnConnect string,
runOnConnectRestart bool,
runOnDisconnect string,
externalCmdPool *externalcmd.Pool,
pathManager *pathManager,
conn *gortsplib.ServerConn,
parent rtspConnParent,
) *rtspConn {
c := &rtspConn{
isTLS: isTLS,
rtspAddress: rtspAddress,
authMethods: authMethods,
readTimeout: readTimeout,
pathManager: pathManager,
rconn: conn,
parent: parent,
uuid: uuid.New(),
created: time.Now(),
}
c.conn = newConn(
rtspAddress,
runOnConnect,
runOnConnectRestart,
runOnDisconnect,
externalCmdPool,
c,
)
c.Log(logger.Info, "opened")
c.conn.open(defs.APIPathSourceOrReader{
Type: func() string {
if isTLS {
return "rtspsConn"
}
return "rtspConn"
}(),
ID: c.uuid.String(),
})
return c
}
func (c *rtspConn) Log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.rconn.NetConn().RemoteAddr()}, args...)...)
}
// Conn returns the RTSP connection.
func (c *rtspConn) Conn() *gortsplib.ServerConn {
return c.rconn
}
func (c *rtspConn) remoteAddr() net.Addr {
return c.rconn.NetConn().RemoteAddr()
}
func (c *rtspConn) ip() net.IP {
return c.rconn.NetConn().RemoteAddr().(*net.TCPAddr).IP
}
// onClose is called by rtspServer.
func (c *rtspConn) onClose(err error) {
c.Log(logger.Info, "closed: %v", err)
c.conn.close()
}
// onRequest is called by rtspServer.
func (c *rtspConn) onRequest(req *base.Request) {
c.Log(logger.Debug, "[c->s] %v", req)
}
// OnResponse is called by rtspServer.
func (c *rtspConn) OnResponse(res *base.Response) {
c.Log(logger.Debug, "[s->c] %v", res)
}
// onDescribe is called by rtspServer.
func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
) (*base.Response, *gortsplib.ServerStream, error) {
if len(ctx.Path) == 0 || ctx.Path[0] != '/' {
return &base.Response{
StatusCode: base.StatusBadRequest,
}, nil, fmt.Errorf("invalid path")
}
ctx.Path = ctx.Path[1:]
if c.authNonce == "" {
var err error
c.authNonce, err = auth.GenerateNonce()
if err != nil {
return &base.Response{
StatusCode: base.StatusInternalServerError,
}, nil, err
}
}
res := c.pathManager.describe(pathDescribeReq{
accessRequest: pathAccessRequest{
name: ctx.Path,
query: ctx.Query,
ip: c.ip(),
proto: authProtocolRTSP,
id: &c.uuid,
rtspRequest: ctx.Request,
rtspNonce: c.authNonce,
},
})
if res.err != nil {
switch terr := res.err.(type) {
case *errAuthentication:
res, err := c.handleAuthError(terr)
return res, nil, err
case errPathNoOnePublishing:
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, res.err
default:
return &base.Response{
StatusCode: base.StatusBadRequest,
}, nil, res.err
}
}
if res.redirect != "" {
return &base.Response{
StatusCode: base.StatusMovedPermanently,
Header: base.Header{
"Location": base.HeaderValue{res.redirect},
},
}, nil, nil
}
var stream *gortsplib.ServerStream
if !c.parent.getISTLS() {
stream = res.stream.RTSPStream(c.parent.getServer())
} else {
stream = res.stream.RTSPSStream(c.parent.getServer())
}
return &base.Response{
StatusCode: base.StatusOK,
}, stream, nil
}
func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) {
c.authFailures++
// VLC with login prompt sends 4 requests:
// 1) without credentials
// 2) with password but without username
// 3) without credentials
// 4) with password and username
// therefore we must allow up to 3 failures
if c.authFailures <= 3 {
return &base.Response{
StatusCode: base.StatusUnauthorized,
Header: base.Header{
"WWW-Authenticate": auth.GenerateWWWAuthenticate(c.authMethods, "IPCAM", c.authNonce),
},
}, nil
}
// wait some seconds to stop brute force attacks
<-time.After(rtspPauseAfterAuthError)
return &base.Response{
StatusCode: base.StatusUnauthorized,
}, authErr
}
func (c *rtspConn) apiItem() *defs.APIRTSPConn {
return &defs.APIRTSPConn{
ID: c.uuid,
Created: c.created,
RemoteAddr: c.remoteAddr().String(),
BytesReceived: c.rconn.BytesReceived(),
BytesSent: c.rconn.BytesSent(),
}
}