mediamtx/internal/core/path_manager.go

356 lines
8.1 KiB
Go
Raw Normal View History

package core
2020-10-19 20:17:48 +00:00
import (
2021-05-10 19:32:59 +00:00
"context"
2020-10-19 20:17:48 +00:00
"fmt"
2021-05-07 21:07:31 +00:00
"net"
2020-10-19 20:17:48 +00:00
"sync"
"time"
2021-05-07 21:07:31 +00:00
"github.com/aler9/gortsplib/pkg/base"
2020-11-15 16:56:54 +00:00
"github.com/aler9/gortsplib/pkg/headers"
2020-10-19 20:17:48 +00:00
2020-11-01 21:56:56 +00:00
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/logger"
2020-10-19 20:17:48 +00:00
)
type pathManagerParent interface {
Log(logger.Level, string, ...interface{})
OnPathSourceReady(*path)
2020-10-19 20:17:48 +00:00
}
type pathManager struct {
rtspAddress string
2021-01-10 11:55:53 +00:00
readTimeout time.Duration
writeTimeout time.Duration
readBufferCount int
readBufferSize int
2021-01-10 11:55:53 +00:00
authMethods []headers.AuthMethod
pathConfs map[string]*conf.PathConf
stats *stats
parent pathManagerParent
2020-10-19 20:17:48 +00:00
2021-05-10 19:32:59 +00:00
ctx context.Context
ctxCancel func()
wg sync.WaitGroup
paths map[string]*path
2020-10-19 20:17:48 +00:00
// in
2021-05-09 12:41:18 +00:00
confReload chan map[string]*conf.PathConf
pathClose chan *path
rpDescribe chan readPublisherDescribeReq
rpSetupPlay chan readPublisherSetupPlayReq
rpAnnounce chan readPublisherAnnounceReq
2020-10-19 20:17:48 +00:00
}
func newPathManager(
parentCtx context.Context,
rtspAddress string,
2020-10-19 20:17:48 +00:00
readTimeout time.Duration,
writeTimeout time.Duration,
readBufferCount int,
readBufferSize int,
2020-10-19 20:17:48 +00:00
authMethods []headers.AuthMethod,
pathConfs map[string]*conf.PathConf,
stats *stats,
parent pathManagerParent) *pathManager {
ctx, ctxCancel := context.WithCancel(parentCtx)
2021-05-10 19:32:59 +00:00
pm := &pathManager{
rtspAddress: rtspAddress,
2020-10-19 20:17:48 +00:00
readTimeout: readTimeout,
writeTimeout: writeTimeout,
2021-01-10 11:55:53 +00:00
readBufferCount: readBufferCount,
readBufferSize: readBufferSize,
2020-10-19 20:17:48 +00:00
authMethods: authMethods,
pathConfs: pathConfs,
stats: stats,
2020-10-19 20:17:48 +00:00
parent: parent,
2021-05-10 19:32:59 +00:00
ctx: ctx,
ctxCancel: ctxCancel,
paths: make(map[string]*path),
confReload: make(chan map[string]*conf.PathConf),
pathClose: make(chan *path),
rpDescribe: make(chan readPublisherDescribeReq),
rpSetupPlay: make(chan readPublisherSetupPlayReq),
rpAnnounce: make(chan readPublisherAnnounceReq),
2020-10-19 20:17:48 +00:00
}
pm.createPaths()
2020-10-19 20:17:48 +00:00
2021-05-10 19:32:59 +00:00
pm.wg.Add(1)
2020-10-19 20:17:48 +00:00
go pm.run()
2021-05-10 19:32:59 +00:00
2020-10-19 20:17:48 +00:00
return pm
}
func (pm *pathManager) close() {
2021-05-10 19:32:59 +00:00
pm.ctxCancel()
pm.wg.Wait()
2020-10-19 20:17:48 +00:00
}
2020-11-05 11:30:25 +00:00
// Log is the main logging function.
func (pm *pathManager) Log(level logger.Level, format string, args ...interface{}) {
pm.parent.Log(level, format, args...)
2020-10-19 20:17:48 +00:00
}
func (pm *pathManager) run() {
2021-05-10 19:32:59 +00:00
defer pm.wg.Done()
2020-10-19 20:17:48 +00:00
outer:
for {
select {
case pathConfs := <-pm.confReload:
// remove confs
for pathName := range pm.pathConfs {
if _, ok := pathConfs[pathName]; !ok {
delete(pm.pathConfs, pathName)
}
}
// update confs
for pathName, oldConf := range pm.pathConfs {
if !oldConf.Equal(pathConfs[pathName]) {
pm.pathConfs[pathName] = pathConfs[pathName]
}
}
// add confs
for pathName, pathConf := range pathConfs {
if _, ok := pm.pathConfs[pathName]; !ok {
pm.pathConfs[pathName] = pathConf
}
}
// remove paths associated with a conf which doesn't exist anymore
// or has changed
for _, pa := range pm.paths {
if pathConf, ok := pm.pathConfs[pa.ConfName()]; !ok || pathConf != pa.Conf() {
delete(pm.paths, pa.Name())
pa.Close()
}
}
// add paths
pm.createPaths()
2020-10-19 20:17:48 +00:00
case pa := <-pm.pathClose:
2021-05-10 19:32:59 +00:00
if pmpa, ok := pm.paths[pa.Name()]; !ok || pmpa != pa {
continue
}
2020-10-19 20:17:48 +00:00
delete(pm.paths, pa.Name())
pa.Close()
2021-05-09 12:41:18 +00:00
case req := <-pm.rpDescribe:
pathName, pathConf, err := pm.findPathConf(req.PathName)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- readPublisherDescribeRes{Err: err}
2020-10-19 20:17:48 +00:00
continue
}
2021-05-07 21:07:31 +00:00
err = pm.authenticate(
req.IP,
req.ValidateCredentials,
2021-03-10 14:06:45 +00:00
req.PathName,
2021-05-07 21:07:31 +00:00
pathConf.ReadIPsParsed,
2021-03-10 14:06:45 +00:00
pathConf.ReadUser,
pathConf.ReadPass,
2021-05-07 21:07:31 +00:00
)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- readPublisherDescribeRes{Err: err}
2020-10-19 20:17:48 +00:00
continue
}
// create path if it doesn't exist
if _, ok := pm.paths[req.PathName]; !ok {
2021-03-10 14:06:45 +00:00
pm.createPath(pathName, pathConf, req.PathName)
2020-10-19 20:17:48 +00:00
}
pm.paths[req.PathName].OnPathManDescribe(req)
2021-05-09 12:41:18 +00:00
case req := <-pm.rpSetupPlay:
pathName, pathConf, err := pm.findPathConf(req.PathName)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- readPublisherSetupPlayRes{Err: err}
2020-10-19 20:17:48 +00:00
continue
}
2021-05-07 21:07:31 +00:00
err = pm.authenticate(
req.IP,
req.ValidateCredentials,
2021-03-10 14:06:45 +00:00
req.PathName,
2021-05-07 21:07:31 +00:00
pathConf.ReadIPsParsed,
2021-03-10 14:06:45 +00:00
pathConf.ReadUser,
pathConf.ReadPass,
2021-05-07 21:07:31 +00:00
)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- readPublisherSetupPlayRes{Err: err}
2020-10-19 20:17:48 +00:00
continue
}
// create path if it doesn't exist
if _, ok := pm.paths[req.PathName]; !ok {
2021-03-10 14:06:45 +00:00
pm.createPath(pathName, pathConf, req.PathName)
2020-10-19 20:17:48 +00:00
}
2021-03-10 14:06:45 +00:00
pm.paths[req.PathName].OnPathManSetupPlay(req)
2020-10-19 20:17:48 +00:00
2021-05-09 12:41:18 +00:00
case req := <-pm.rpAnnounce:
pathName, pathConf, err := pm.findPathConf(req.PathName)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- readPublisherAnnounceRes{Err: err}
2020-10-19 20:17:48 +00:00
continue
}
2021-05-07 21:07:31 +00:00
err = pm.authenticate(
req.IP,
req.ValidateCredentials,
req.PathName,
2021-05-07 21:07:31 +00:00
pathConf.PublishIPsParsed,
2021-03-10 14:06:45 +00:00
pathConf.PublishUser,
pathConf.PublishPass,
2021-05-07 21:07:31 +00:00
)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- readPublisherAnnounceRes{Err: err}
2020-10-19 20:17:48 +00:00
continue
}
// create path if it doesn't exist
if _, ok := pm.paths[req.PathName]; !ok {
2021-03-10 14:06:45 +00:00
pm.createPath(pathName, pathConf, req.PathName)
}
2021-03-10 14:06:45 +00:00
pm.paths[req.PathName].OnPathManAnnounce(req)
2020-10-19 20:17:48 +00:00
2021-05-10 19:32:59 +00:00
case <-pm.ctx.Done():
2020-10-19 20:17:48 +00:00
break outer
}
}
2021-05-10 19:32:59 +00:00
pm.ctxCancel()
2020-10-19 20:17:48 +00:00
}
func (pm *pathManager) createPath(confName string, conf *conf.PathConf, name string) {
pm.paths[name] = newPath(
2021-05-11 15:20:32 +00:00
pm.ctx,
pm.rtspAddress,
pm.readTimeout,
pm.writeTimeout,
pm.readBufferCount,
pm.readBufferSize,
confName,
conf,
name,
&pm.wg,
pm.stats,
pm)
}
func (pm *pathManager) createPaths() {
for pathName, pathConf := range pm.pathConfs {
if _, ok := pm.paths[pathName]; !ok && pathConf.Regexp == nil {
2021-03-10 14:06:45 +00:00
pm.createPath(pathName, pathConf, pathName)
}
}
}
func (pm *pathManager) findPathConf(name string) (string, *conf.PathConf, error) {
2020-10-19 20:17:48 +00:00
err := conf.CheckPathName(name)
if err != nil {
return "", nil, fmt.Errorf("invalid path name: %s (%s)", err, name)
2020-10-19 20:17:48 +00:00
}
// normal path
if pathConf, ok := pm.pathConfs[name]; ok {
return name, pathConf, nil
2020-10-19 20:17:48 +00:00
}
// regular expression path
for pathName, pathConf := range pm.pathConfs {
2020-10-19 20:17:48 +00:00
if pathConf.Regexp != nil && pathConf.Regexp.MatchString(name) {
return pathName, pathConf, nil
2020-10-19 20:17:48 +00:00
}
}
return "", nil, fmt.Errorf("unable to find a valid configuration for path '%s'", name)
}
// OnConfReload is called by core.
func (pm *pathManager) OnConfReload(pathConfs map[string]*conf.PathConf) {
2021-05-10 19:32:59 +00:00
select {
case pm.confReload <- pathConfs:
case <-pm.ctx.Done():
}
2020-10-19 20:17:48 +00:00
}
// OnPathSourceReady is called by path.
func (pm *pathManager) OnPathSourceReady(pa *path) {
pm.parent.OnPathSourceReady(pa)
}
// OnPathClose is called by path.
func (pm *pathManager) OnPathClose(pa *path) {
2021-05-10 19:32:59 +00:00
select {
case pm.pathClose <- pa:
case <-pm.ctx.Done():
}
2020-10-19 20:17:48 +00:00
}
2021-05-09 12:41:18 +00:00
// OnReadPublisherDescribe is called by a ReadPublisher.
func (pm *pathManager) OnReadPublisherDescribe(req readPublisherDescribeReq) {
2021-05-10 19:32:59 +00:00
select {
case pm.rpDescribe <- req:
case <-pm.ctx.Done():
req.Res <- readPublisherDescribeRes{Err: fmt.Errorf("terminated")}
2021-05-10 19:32:59 +00:00
}
2020-10-19 20:17:48 +00:00
}
2021-05-09 12:41:18 +00:00
// OnReadPublisherAnnounce is called by a ReadPublisher.
func (pm *pathManager) OnReadPublisherAnnounce(req readPublisherAnnounceReq) {
2021-05-10 19:32:59 +00:00
select {
case pm.rpAnnounce <- req:
case <-pm.ctx.Done():
req.Res <- readPublisherAnnounceRes{Err: fmt.Errorf("terminated")}
2021-05-10 19:32:59 +00:00
}
2020-10-19 20:17:48 +00:00
}
2021-05-09 12:41:18 +00:00
// OnReadPublisherSetupPlay is called by a ReadPublisher.
func (pm *pathManager) OnReadPublisherSetupPlay(req readPublisherSetupPlayReq) {
2021-05-10 19:32:59 +00:00
select {
case pm.rpSetupPlay <- req:
case <-pm.ctx.Done():
req.Res <- readPublisherSetupPlayRes{Err: fmt.Errorf("terminated")}
2021-05-10 19:32:59 +00:00
}
2020-10-19 20:17:48 +00:00
}
2021-05-07 21:07:31 +00:00
func (pm *pathManager) authenticate(
2021-05-07 21:07:31 +00:00
ip net.IP,
validateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error,
pathName string,
pathIPs []interface{},
pathUser string,
pathPass string,
) error {
// validate ip
if pathIPs != nil && ip != nil {
if !ipEqualOrInRange(ip, pathIPs) {
return readPublisherErrAuthCritical{
2021-05-07 21:07:31 +00:00
Message: fmt.Sprintf("IP '%s' not allowed", ip),
Response: &base.Response{
StatusCode: base.StatusUnauthorized,
},
}
}
}
// validate user
if pathUser != "" && validateCredentials != nil {
err := validateCredentials(pm.authMethods, pathUser, pathPass)
if err != nil {
return err
}
}
return nil
}