mediamtx/internal/servers/rtsp/conn.go

214 lines
5.1 KiB
Go
Raw Normal View History

package rtsp
2019-12-31 12:48:17 +00:00
import (
"fmt"
2019-12-31 12:48:17 +00:00
"net"
"time"
2019-12-31 12:48:17 +00:00
2023-08-26 16:54:28 +00:00
"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"
2023-05-16 14:14:20 +00:00
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
2023-05-16 14:14:20 +00:00
"github.com/bluenviron/mediamtx/internal/externalcmd"
"github.com/bluenviron/mediamtx/internal/hooks"
2023-05-16 14:14:20 +00:00
"github.com/bluenviron/mediamtx/internal/logger"
2019-12-31 12:48:17 +00:00
)
const (
rtspPauseAfterAuthError = 2 * time.Second
)
type conn struct {
isTLS bool
rtspAddress string
authMethods []headers.AuthMethod
readTimeout conf.StringDuration
runOnConnect string
runOnConnectRestart bool
runOnDisconnect string
externalCmdPool *externalcmd.Pool
pathManager defs.PathManager
rconn *gortsplib.ServerConn
rserver *gortsplib.Server
parent *Server
uuid uuid.UUID
created time.Time
onDisconnectHook func()
authNonce string
authFailures int
2019-12-31 12:48:17 +00:00
}
func (c *conn) initialize() {
c.uuid = uuid.New()
c.created = time.Now()
2019-12-31 12:48:17 +00:00
c.Log(logger.Info, "opened")
desc := defs.APIPathSourceOrReader{
Type: func() string {
if c.isTLS {
return "rtspsConn"
}
return "conn"
}(),
ID: c.uuid.String(),
}
c.onDisconnectHook = hooks.OnConnect(hooks.OnConnectParams{
Logger: c,
ExternalCmdPool: c.externalCmdPool,
RunOnConnect: c.runOnConnect,
RunOnConnectRestart: c.runOnConnectRestart,
RunOnDisconnect: c.runOnDisconnect,
RTSPAddress: c.rtspAddress,
Desc: desc,
})
2019-12-31 12:48:17 +00:00
}
// Log implements logger.Writer.
func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.rconn.NetConn().RemoteAddr()}, args...)...)
2020-10-19 20:17:48 +00:00
}
2021-05-07 21:07:31 +00:00
// Conn returns the RTSP connection.
func (c *conn) Conn() *gortsplib.ServerConn {
return c.rconn
2021-05-07 21:07:31 +00:00
}
func (c *conn) remoteAddr() net.Addr {
return c.rconn.NetConn().RemoteAddr()
}
func (c *conn) ip() net.IP {
return c.rconn.NetConn().RemoteAddr().(*net.TCPAddr).IP
}
2021-10-27 19:01:00 +00:00
// onClose is called by rtspServer.
func (c *conn) onClose(err error) {
c.Log(logger.Info, "closed: %v", err)
c.onDisconnectHook()
}
2021-10-27 19:01:00 +00:00
// onRequest is called by rtspServer.
func (c *conn) onRequest(req *base.Request) {
c.Log(logger.Debug, "[c->s] %v", req)
}
// OnResponse is called by rtspServer.
func (c *conn) OnResponse(res *base.Response) {
c.Log(logger.Debug, "[s->c] %v", res)
}
2021-10-27 19:01:00 +00:00
// onDescribe is called by rtspServer.
func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
2021-09-09 21:05:54 +00:00
) (*base.Response, *gortsplib.ServerStream, error) {
2023-01-11 00:11:05 +00:00
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 == "" {
2023-07-30 21:39:24 +00:00
var err error
2023-08-26 16:54:28 +00:00
c.authNonce, err = auth.GenerateNonce()
2023-07-30 21:39:24 +00:00
if err != nil {
return &base.Response{
StatusCode: base.StatusInternalServerError,
}, nil, err
}
}
res := c.pathManager.Describe(defs.PathDescribeReq{
AccessRequest: defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: defs.AuthProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
},
})
if res.Err != nil {
switch terr := res.Err.(type) {
case *defs.ErrAuthentication:
res, err := c.handleAuthError(terr)
return res, nil, err
case defs.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
}
2023-08-26 16:54:28 +00:00
var stream *gortsplib.ServerStream
if !c.isTLS {
stream = res.Stream.RTSPStream(c.rserver)
2023-08-26 16:54:28 +00:00
} else {
stream = res.Stream.RTSPSStream(c.rserver)
2023-08-26 16:54:28 +00:00
}
return &base.Response{
StatusCode: base.StatusOK,
2023-08-26 16:54:28 +00:00
}, stream, nil
}
func (c *conn) 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 *conn) apiItem() *defs.APIRTSPConn {
return &defs.APIRTSPConn{
ID: c.uuid,
Created: c.created,
RemoteAddr: c.remoteAddr().String(),
BytesReceived: c.rconn.BytesReceived(),
BytesSent: c.rconn.BytesSent(),
}
}