2023-12-08 18:17:17 +00:00
|
|
|
package rtsp
|
2019-12-31 12:48:17 +00:00
|
|
|
|
|
|
|
import (
|
2021-12-22 18:13:56 +00:00
|
|
|
"fmt"
|
2019-12-31 12:48:17 +00:00
|
|
|
"net"
|
2020-06-21 09:30:30 +00:00
|
|
|
"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"
|
2022-11-09 18:31:52 +00:00
|
|
|
"github.com/google/uuid"
|
2020-10-13 22:02:55 +00:00
|
|
|
|
2023-05-16 14:14:20 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/conf"
|
2023-10-31 13:19:04 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/defs"
|
2023-05-16 14:14:20 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
2023-11-26 21:06:07 +00:00
|
|
|
"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
|
|
|
)
|
|
|
|
|
2020-06-21 09:30:30 +00:00
|
|
|
const (
|
2023-07-23 18:18:58 +00:00
|
|
|
rtspPauseAfterAuthError = 2 * time.Second
|
2020-06-21 09:30:30 +00:00
|
|
|
)
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
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
|
2023-05-08 15:04:14 +00:00
|
|
|
|
2023-11-26 21:06:07 +00:00
|
|
|
uuid uuid.UUID
|
|
|
|
created time.Time
|
|
|
|
onDisconnectHook func()
|
|
|
|
authNonce string
|
|
|
|
authFailures int
|
2019-12-31 12:48:17 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) initialize() {
|
|
|
|
c.uuid = uuid.New()
|
|
|
|
c.created = time.Now()
|
2019-12-31 12:48:17 +00:00
|
|
|
|
2023-05-04 18:16:41 +00:00
|
|
|
c.Log(logger.Info, "opened")
|
2020-10-14 17:35:21 +00:00
|
|
|
|
2023-11-26 21:06:07 +00:00
|
|
|
desc := defs.APIPathSourceOrReader{
|
2023-09-16 19:41:49 +00:00
|
|
|
Type: func() string {
|
2023-12-08 18:17:17 +00:00
|
|
|
if c.isTLS {
|
2023-09-16 19:41:49 +00:00
|
|
|
return "rtspsConn"
|
|
|
|
}
|
2023-12-08 18:17:17 +00:00
|
|
|
return "conn"
|
2023-09-16 19:41:49 +00:00
|
|
|
}(),
|
|
|
|
ID: c.uuid.String(),
|
2023-11-26 21:06:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c.onDisconnectHook = hooks.OnConnect(hooks.OnConnectParams{
|
|
|
|
Logger: c,
|
2023-12-08 18:17:17 +00:00
|
|
|
ExternalCmdPool: c.externalCmdPool,
|
|
|
|
RunOnConnect: c.runOnConnect,
|
|
|
|
RunOnConnectRestart: c.runOnConnectRestart,
|
|
|
|
RunOnDisconnect: c.runOnDisconnect,
|
|
|
|
RTSPAddress: c.rtspAddress,
|
2023-11-26 21:06:07 +00:00
|
|
|
Desc: desc,
|
2023-09-16 19:41:49 +00:00
|
|
|
})
|
2019-12-31 12:48:17 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
// Log implements logger.Writer.
|
|
|
|
func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
|
2023-09-16 17:21:48 +00:00
|
|
|
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.rconn.NetConn().RemoteAddr()}, args...)...)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2020-07-29 21:30:42 +00:00
|
|
|
|
2021-05-07 21:07:31 +00:00
|
|
|
// Conn returns the RTSP connection.
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) Conn() *gortsplib.ServerConn {
|
2023-09-16 17:21:48 +00:00
|
|
|
return c.rconn
|
2021-05-07 21:07:31 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) remoteAddr() net.Addr {
|
2023-09-16 17:21:48 +00:00
|
|
|
return c.rconn.NetConn().RemoteAddr()
|
2022-11-09 17:31:31 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) ip() net.IP {
|
2023-09-16 17:21:48 +00:00
|
|
|
return c.rconn.NetConn().RemoteAddr().(*net.TCPAddr).IP
|
2020-05-03 21:26:41 +00:00
|
|
|
}
|
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
// onClose is called by rtspServer.
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) onClose(err error) {
|
2023-09-09 21:37:56 +00:00
|
|
|
c.Log(logger.Info, "closed: %v", err)
|
2021-08-12 08:50:29 +00:00
|
|
|
|
2023-11-26 21:06:07 +00:00
|
|
|
c.onDisconnectHook()
|
2021-08-12 08:50:29 +00:00
|
|
|
}
|
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
// onRequest is called by rtspServer.
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) onRequest(req *base.Request) {
|
2023-05-04 18:16:41 +00:00
|
|
|
c.Log(logger.Debug, "[c->s] %v", req)
|
2021-07-30 18:13:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// OnResponse is called by rtspServer.
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) OnResponse(res *base.Response) {
|
2023-05-04 18:16:41 +00:00
|
|
|
c.Log(logger.Debug, "[s->c] %v", res)
|
2021-07-30 18:13:17 +00:00
|
|
|
}
|
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
// onDescribe is called by rtspServer.
|
2023-12-08 18:17:17 +00:00
|
|
|
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:]
|
|
|
|
|
2023-05-08 15:04:14 +00:00
|
|
|
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
|
|
|
|
}
|
2023-05-08 15:04:14 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
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,
|
2021-07-30 18:13:17 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
if res.Err != nil {
|
|
|
|
switch terr := res.Err.(type) {
|
|
|
|
case *defs.ErrAuthentication:
|
2023-07-23 18:06:16 +00:00
|
|
|
res, err := c.handleAuthError(terr)
|
2023-05-08 15:04:14 +00:00
|
|
|
return res, nil, err
|
2021-07-30 18:13:17 +00:00
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
case defs.ErrPathNoOnePublishing:
|
2021-07-30 18:13:17 +00:00
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusNotFound,
|
2023-12-08 18:17:17 +00:00
|
|
|
}, nil, res.Err
|
2021-07-30 18:13:17 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusBadRequest,
|
2023-12-08 18:17:17 +00:00
|
|
|
}, nil, res.Err
|
2021-07-30 18:13:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
if res.Redirect != "" {
|
2021-07-30 18:13:17 +00:00
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusMovedPermanently,
|
|
|
|
Header: base.Header{
|
2023-12-08 18:17:17 +00:00
|
|
|
"Location": base.HeaderValue{res.Redirect},
|
2021-07-30 18:13:17 +00:00
|
|
|
},
|
|
|
|
}, nil, nil
|
|
|
|
}
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
var stream *gortsplib.ServerStream
|
2023-12-08 18:17:17 +00:00
|
|
|
if !c.isTLS {
|
|
|
|
stream = res.Stream.RTSPStream(c.rserver)
|
2023-08-26 16:54:28 +00:00
|
|
|
} else {
|
2023-12-08 18:17:17 +00:00
|
|
|
stream = res.Stream.RTSPSStream(c.rserver)
|
2023-08-26 16:54:28 +00:00
|
|
|
}
|
|
|
|
|
2021-07-30 18:13:17 +00:00
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusOK,
|
2023-08-26 16:54:28 +00:00
|
|
|
}, stream, nil
|
2021-07-30 18:13:17 +00:00
|
|
|
}
|
2023-05-08 15:04:14 +00:00
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) handleAuthError(authErr error) (*base.Response, error) {
|
2023-05-08 15:04:14 +00:00
|
|
|
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
|
2023-07-23 18:18:58 +00:00
|
|
|
<-time.After(rtspPauseAfterAuthError)
|
2023-05-08 15:04:14 +00:00
|
|
|
|
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusUnauthorized,
|
|
|
|
}, authErr
|
|
|
|
}
|
2023-05-18 13:07:47 +00:00
|
|
|
|
2023-12-08 18:17:17 +00:00
|
|
|
func (c *conn) apiItem() *defs.APIRTSPConn {
|
2023-10-31 13:19:04 +00:00
|
|
|
return &defs.APIRTSPConn{
|
2023-05-18 13:07:47 +00:00
|
|
|
ID: c.uuid,
|
|
|
|
Created: c.created,
|
|
|
|
RemoteAddr: c.remoteAddr().String(),
|
2023-09-16 17:21:48 +00:00
|
|
|
BytesReceived: c.rconn.BytesReceived(),
|
|
|
|
BytesSent: c.rconn.BytesSent(),
|
2023-05-18 13:07:47 +00:00
|
|
|
}
|
|
|
|
}
|