mediamtx/internal/core/path_manager.go

427 lines
9.6 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-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
)
2021-08-11 10:45:53 +00:00
type pathManagerHLSServer interface {
OnPathSourceReady(pa *path)
}
type pathManagerParent interface {
Log(logger.Level, string, ...interface{})
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
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
2021-08-11 10:45:53 +00:00
hlsServer pathManagerHLSServer
paths map[string]*path
2020-10-19 20:17:48 +00:00
// in
confReload chan map[string]*conf.PathConf
pathClose chan *path
pathSourceReady chan *path
describe chan pathDescribeReq
readerSetupPlay chan pathReaderSetupPlayReq
publisherAnnounce chan pathPublisherAnnounceReq
2021-08-11 10:45:53 +00:00
hlsServerSet chan pathManagerHLSServer
apiPathsList chan apiPathsListReq1
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,
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,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
readBufferCount: readBufferCount,
readBufferSize: readBufferSize,
pathConfs: pathConfs,
stats: stats,
parent: parent,
ctx: ctx,
ctxCancel: ctxCancel,
paths: make(map[string]*path),
confReload: make(chan map[string]*conf.PathConf),
pathClose: make(chan *path),
pathSourceReady: make(chan *path),
describe: make(chan pathDescribeReq),
readerSetupPlay: make(chan pathReaderSetupPlayReq),
publisherAnnounce: make(chan pathPublisherAnnounceReq),
2021-08-11 10:45:53 +00:00
hlsServerSet: make(chan pathManagerHLSServer),
apiPathsList: make(chan apiPathsListReq1),
2020-10-19 20:17:48 +00:00
}
2021-08-01 15:22:28 +00:00
for pathName, pathConf := range pm.pathConfs {
if pathConf.Regexp == nil {
pm.createPath(pathName, pathConf, pathName)
}
}
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()
}
}
2021-08-01 15:22:28 +00:00
// add new paths
for pathName, pathConf := range pm.pathConfs {
if _, ok := pm.paths[pathName]; !ok && pathConf.Regexp == nil {
pm.createPath(pathName, pathConf, pathName)
}
}
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()
case pa := <-pm.pathSourceReady:
if pm.hlsServer != nil {
pm.hlsServer.OnPathSourceReady(pa)
}
case req := <-pm.describe:
pathName, pathConf, err := pm.findPathConf(req.PathName)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- pathDescribeRes{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 <- pathDescribeRes{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-08-01 15:22:28 +00:00
req.Res <- pathDescribeRes{Path: pm.paths[req.PathName]}
2020-10-19 20:17:48 +00:00
case req := <-pm.readerSetupPlay:
pathName, pathConf, err := pm.findPathConf(req.PathName)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- pathReaderSetupPlayRes{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 <- pathReaderSetupPlayRes{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-08-01 15:22:28 +00:00
req.Res <- pathReaderSetupPlayRes{Path: pm.paths[req.PathName]}
2020-10-19 20:17:48 +00:00
case req := <-pm.publisherAnnounce:
pathName, pathConf, err := pm.findPathConf(req.PathName)
2020-10-19 20:17:48 +00:00
if err != nil {
req.Res <- pathPublisherAnnounceRes{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 <- pathPublisherAnnounceRes{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-08-01 15:22:28 +00:00
req.Res <- pathPublisherAnnounceRes{Path: pm.paths[req.PathName]}
2020-10-19 20:17:48 +00:00
case s := <-pm.hlsServerSet:
pm.hlsServer = s
case req := <-pm.apiPathsList:
paths := make(map[string]*path)
for name, pa := range pm.paths {
paths[name] = pa
}
req.Res <- apiPathsListRes1{
Paths: paths,
}
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) 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)
}
func (pm *pathManager) authenticate(
ip net.IP,
validateCredentials func(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 pathErrAuthCritical{
Message: fmt.Sprintf("IP '%s' not allowed", ip),
Response: &base.Response{
StatusCode: base.StatusUnauthorized,
},
}
}
}
// validate user
if pathUser != "" && validateCredentials != nil {
err := validateCredentials(pathUser, pathPass)
if err != nil {
return err
}
}
return nil
}
// 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) {
select {
case pm.pathSourceReady <- pa:
case <-pm.ctx.Done():
}
}
// 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
}
// OnDescribe is called by a reader or publisher.
func (pm *pathManager) OnDescribe(req pathDescribeReq) pathDescribeRes {
req.Res = make(chan pathDescribeRes)
2021-05-10 19:32:59 +00:00
select {
case pm.describe <- req:
2021-08-01 15:22:28 +00:00
res := <-req.Res
if res.Err != nil {
return res
}
return res.Path.OnDescribe(req)
2021-05-10 19:32:59 +00:00
case <-pm.ctx.Done():
return pathDescribeRes{Err: fmt.Errorf("terminated")}
2021-05-10 19:32:59 +00:00
}
2020-10-19 20:17:48 +00:00
}
// OnPublisherAnnounce is called by a publisher.
func (pm *pathManager) OnPublisherAnnounce(req pathPublisherAnnounceReq) pathPublisherAnnounceRes {
req.Res = make(chan pathPublisherAnnounceRes)
2021-05-10 19:32:59 +00:00
select {
case pm.publisherAnnounce <- req:
2021-08-01 15:22:28 +00:00
res := <-req.Res
if res.Err != nil {
return res
}
return res.Path.OnPublisherAnnounce(req)
2021-05-10 19:32:59 +00:00
case <-pm.ctx.Done():
return pathPublisherAnnounceRes{Err: fmt.Errorf("terminated")}
2021-05-10 19:32:59 +00:00
}
2020-10-19 20:17:48 +00:00
}
// OnReaderSetupPlay is called by a reader.
func (pm *pathManager) OnReaderSetupPlay(req pathReaderSetupPlayReq) pathReaderSetupPlayRes {
req.Res = make(chan pathReaderSetupPlayRes)
2021-05-10 19:32:59 +00:00
select {
case pm.readerSetupPlay <- req:
2021-08-01 15:22:28 +00:00
res := <-req.Res
if res.Err != nil {
return res
}
return res.Path.OnReaderSetupPlay(req)
2021-05-10 19:32:59 +00:00
case <-pm.ctx.Done():
return pathReaderSetupPlayRes{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
// OnHLSServer is called by hlsServer.
2021-08-11 10:45:53 +00:00
func (pm *pathManager) OnHLSServer(s pathManagerHLSServer) {
select {
case pm.hlsServerSet <- s:
case <-pm.ctx.Done():
}
}
// OnAPIPathsList is called by api.
func (pm *pathManager) OnAPIPathsList(req apiPathsListReq1) apiPathsListRes1 {
req.Res = make(chan apiPathsListRes1)
select {
case pm.apiPathsList <- req:
return <-req.Res
case <-pm.ctx.Done():
return apiPathsListRes1{Err: fmt.Errorf("terminated")}
}
}