mediamtx/internal/connrtsp/conn.go

254 lines
6.1 KiB
Go
Raw Normal View History

2021-05-09 12:41:18 +00:00
package connrtsp
2019-12-31 12:48:17 +00:00
import (
2020-05-10 20:56:46 +00:00
"errors"
2019-12-31 12:48:17 +00:00
"io"
"net"
"time"
2019-12-31 12:48:17 +00:00
2020-01-20 09:21:05 +00:00
"github.com/aler9/gortsplib"
2020-11-15 16:56:54 +00:00
"github.com/aler9/gortsplib/pkg/auth"
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/gortsplib/pkg/headers"
2021-03-21 10:22:49 +00:00
"github.com/aler9/gortsplib/pkg/liberrors"
2020-11-01 21:56:56 +00:00
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
"github.com/aler9/rtsp-simple-server/internal/logger"
2021-04-27 17:19:47 +00:00
"github.com/aler9/rtsp-simple-server/internal/readpublisher"
2020-11-01 21:56:56 +00:00
"github.com/aler9/rtsp-simple-server/internal/stats"
2019-12-31 12:48:17 +00:00
)
const (
2021-01-06 21:31:08 +00:00
pauseAfterAuthError = 2 * time.Second
)
2021-05-07 21:07:31 +00:00
func isTeardownErr(err error) bool {
_, ok := err.(liberrors.ErrServerSessionTeardown)
return ok
2021-01-31 22:11:14 +00:00
}
func isTerminatedErr(err error) bool {
_, ok := err.(liberrors.ErrServerTerminated)
return ok
}
2021-03-31 19:46:08 +00:00
// PathMan is implemented by pathman.PathMan.
type PathMan interface {
2021-04-27 17:19:47 +00:00
OnReadPublisherDescribe(readpublisher.DescribeReq)
2021-03-31 19:46:08 +00:00
}
2021-04-27 17:19:47 +00:00
// Parent is implemented by serverrtsp.Server.
2020-10-19 20:17:48 +00:00
type Parent interface {
Log(logger.Level, string, ...interface{})
2020-10-19 20:17:48 +00:00
}
2021-05-09 12:41:18 +00:00
// Conn is a RTSP server-side connection.
type Conn struct {
rtspAddress string
readTimeout time.Duration
runOnConnect string
runOnConnectRestart bool
2021-05-07 21:07:31 +00:00
pathMan PathMan
2020-11-01 16:33:06 +00:00
stats *stats.Stats
2020-12-06 17:01:10 +00:00
conn *gortsplib.ServerConn
parent Parent
2020-10-19 20:17:48 +00:00
2021-05-07 21:07:31 +00:00
onConnectCmd *externalcmd.Cmd
authUser string
authPass string
authValidator *auth.Validator
authFailures int
2019-12-31 12:48:17 +00:00
}
2021-05-09 12:41:18 +00:00
// New allocates a Conn.
2020-10-19 20:17:48 +00:00
func New(
rtspAddress string,
2020-10-19 20:17:48 +00:00
readTimeout time.Duration,
runOnConnect string,
runOnConnectRestart bool,
2021-05-07 21:07:31 +00:00
pathMan PathMan,
2020-11-01 16:33:06 +00:00
stats *stats.Stats,
2020-12-06 17:01:10 +00:00
conn *gortsplib.ServerConn,
2021-05-09 12:41:18 +00:00
parent Parent) *Conn {
2020-10-19 20:17:48 +00:00
2021-05-09 12:41:18 +00:00
c := &Conn{
rtspAddress: rtspAddress,
readTimeout: readTimeout,
runOnConnect: runOnConnect,
runOnConnectRestart: runOnConnectRestart,
2021-05-07 21:07:31 +00:00
pathMan: pathMan,
2020-11-01 16:33:06 +00:00
stats: stats,
2020-12-06 17:01:10 +00:00
conn: conn,
parent: parent,
2019-12-31 12:48:17 +00:00
}
2021-05-09 12:41:18 +00:00
c.log(logger.Info, "opened")
2021-05-07 21:07:31 +00:00
if c.runOnConnect != "" {
_, port, _ := net.SplitHostPort(c.rtspAddress)
c.onConnectCmd = externalcmd.New(c.runOnConnect, c.runOnConnectRestart, externalcmd.Environment{
Path: "",
Port: port,
})
}
2020-10-19 20:17:48 +00:00
return c
2019-12-31 12:48:17 +00:00
}
2021-05-09 12:41:18 +00:00
// Close closes a Conn.
func (c *Conn) Close(err error) {
if err != io.EOF && !isTeardownErr(err) && !isTerminatedErr(err) {
2021-05-07 21:07:31 +00:00
c.log(logger.Info, "ERR: %v", err)
}
2021-05-09 12:41:18 +00:00
c.log(logger.Info, "closed")
2021-05-07 21:07:31 +00:00
if c.onConnectCmd != nil {
c.onConnectCmd.Close()
}
2021-04-27 11:43:15 +00:00
}
2021-05-09 12:41:18 +00:00
func (c *Conn) log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr()}, args...)...)
2020-10-19 20:17:48 +00:00
}
2021-05-07 21:07:31 +00:00
// Conn returns the RTSP connection.
2021-05-09 12:41:18 +00:00
func (c *Conn) Conn() *gortsplib.ServerConn {
2021-05-07 21:07:31 +00:00
return c.conn
}
2021-05-09 12:41:18 +00:00
func (c *Conn) ip() net.IP {
return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).IP
}
2021-05-07 21:07:31 +00:00
// OnRequest is called by serverrtsp.Server.
2021-05-09 12:41:18 +00:00
func (c *Conn) OnRequest(req *base.Request) {
2021-05-07 21:07:31 +00:00
c.log(logger.Debug, "[c->s] %v", req)
}
2021-05-07 21:07:31 +00:00
// OnResponse is called by serverrtsp.Server.
2021-05-09 12:41:18 +00:00
func (c *Conn) OnResponse(res *base.Response) {
2021-05-07 21:07:31 +00:00
c.log(logger.Debug, "[s->c] %v", res)
}
2019-12-31 12:48:17 +00:00
2021-05-07 21:07:31 +00:00
// OnDescribe is called by serverrtsp.Server.
2021-05-09 12:41:18 +00:00
func (c *Conn) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, []byte, error) {
2021-05-07 21:07:31 +00:00
resc := make(chan readpublisher.DescribeRes)
c.pathMan.OnReadPublisherDescribe(readpublisher.DescribeReq{
PathName: ctx.Path,
URL: ctx.Req.URL,
IP: c.ip(),
ValidateCredentials: func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error {
return c.ValidateCredentials(authMethods, pathUser, pathPass, ctx.Path, ctx.Req)
},
Res: resc,
})
res := <-resc
2021-03-23 19:37:43 +00:00
2021-05-07 21:07:31 +00:00
if res.Err != nil {
switch terr := res.Err.(type) {
case readpublisher.ErrAuthNotCritical:
return terr.Response, nil, nil
2019-12-31 13:55:46 +00:00
2021-05-07 21:07:31 +00:00
case readpublisher.ErrAuthCritical:
// wait some seconds to stop brute force attacks
<-time.After(pauseAfterAuthError)
2021-05-08 20:30:22 +00:00
return terr.Response, nil, errors.New(terr.Message)
2019-12-31 12:48:17 +00:00
2021-05-07 21:07:31 +00:00
case readpublisher.ErrNoOnePublishing:
return &base.Response{
2021-05-07 21:07:31 +00:00
StatusCode: base.StatusNotFound,
}, nil, res.Err
2019-12-31 13:55:46 +00:00
2021-05-07 21:07:31 +00:00
default:
2021-03-23 19:37:43 +00:00
return &base.Response{
StatusCode: base.StatusBadRequest,
2021-05-07 21:07:31 +00:00
}, nil, res.Err
2021-03-23 19:37:43 +00:00
}
2020-12-13 12:58:56 +00:00
}
2020-01-03 22:05:06 +00:00
2021-05-07 21:07:31 +00:00
if res.Redirect != "" {
return &base.Response{
2021-05-07 21:07:31 +00:00
StatusCode: base.StatusMovedPermanently,
2020-11-07 21:47:10 +00:00
Header: base.Header{
2021-05-07 21:07:31 +00:00
"Location": base.HeaderValue{res.Redirect},
2020-11-07 21:47:10 +00:00
},
2021-05-07 21:07:31 +00:00
}, nil, nil
}
return &base.Response{
StatusCode: base.StatusOK,
}, res.SDP, nil
}
// ValidateCredentials allows to validate the credentials of a path.
2021-05-09 12:41:18 +00:00
func (c *Conn) ValidateCredentials(
2021-05-07 21:07:31 +00:00
authMethods []headers.AuthMethod,
pathUser string,
pathPass string,
pathName string,
req *base.Request,
) error {
// reset authValidator every time the credentials change
if c.authValidator == nil || c.authUser != pathUser || c.authPass != pathPass {
c.authUser = pathUser
c.authPass = pathPass
c.authValidator = auth.NewValidator(pathUser, pathPass, authMethods)
}
// VLC strips the control attribute
// provide an alternative URL without the control attribute
altURL := func() *base.URL {
if req.Method != base.Setup {
return nil
}
return &base.URL{
Scheme: req.URL.Scheme,
Host: req.URL.Host,
Path: "/" + pathName + "/",
}
}()
err := c.authValidator.ValidateHeader(req.Header["Authorization"],
req.Method, req.URL, altURL)
if err != nil {
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 readpublisher.ErrAuthCritical{
Message: "unauthorized: " + err.Error(),
Response: &base.Response{
2020-12-13 12:58:56 +00:00
StatusCode: base.StatusUnauthorized,
Header: base.Header{
2020-12-31 18:47:25 +00:00
"WWW-Authenticate": c.authValidator.GenerateHeader(),
2020-12-13 12:58:56 +00:00
},
2021-05-07 21:07:31 +00:00
},
2020-12-13 12:58:56 +00:00
}
2021-05-07 21:07:31 +00:00
}
2020-12-13 12:58:56 +00:00
2021-05-07 21:07:31 +00:00
if c.authFailures > 1 {
c.log(logger.Debug, "WARN: unauthorized: %s", err)
2020-12-13 12:58:56 +00:00
}
2021-05-07 21:07:31 +00:00
return readpublisher.ErrAuthNotCritical{&base.Response{ //nolint:govet
StatusCode: base.StatusUnauthorized,
Header: base.Header{
"WWW-Authenticate": c.authValidator.GenerateHeader(),
},
}}
2020-07-13 09:12:20 +00:00
}
2020-12-13 12:58:56 +00:00
// login successful, reset authFailures
c.authFailures = 0
return nil
}