feat: rtsp auth

This commit is contained in:
langhuihui
2025-09-18 19:17:31 +08:00
parent 8fb9ba4795
commit d9a8847ba3
6 changed files with 71 additions and 34 deletions

32
plugin/rtsp/BAD_DEVICE.md Normal file
View File

@@ -0,0 +1,32 @@
# 不规范设备耻辱柱
## 宇视
### IPC2A3L-FW-APF60-V1-DT
sdp 中缺失必填字段 (正确格式c=IN IP4 0.0.0.0)
> https://tools.ietf.org/html/rfc4566#section-8.2.6
```sdp
v=0
o=- 1001 1 IN
s=VCP IPC Realtime stream
m=video 0 RTP/AVP 105
c=IN
a=control:rtsp://192.168.0.101/media/video1/video
a=rtpmap:105 H264/90000
a=fmtp:105 profile-level-id=640032; packetization-mode=1; sprop-parameter-sets=Z2QAMqw7UBIAUdCAAAH0AABhqEI=,aO48sA==
a=recvonly
m=audio 0 RTP/AVP 0
c=IN
a=fmtp:0 RTCP=0
a=control:rtsp://192.168.0.101/media/video1/audio1
a=recvonly
m=application 0 RTP/AVP 107
c=IN
a=control:rtsp://192.168.0.101/media/video1/metadata
a=rtpmap:107 vnd.onvif.metadata/90000
a=fmtp:107 DecoderTag=h3c-v3 RTCP=0
a=recvonly
```

View File

@@ -24,6 +24,8 @@ var _ = m7s.InstallPlugin[RTSPPlugin](m7s.PluginMeta{
type RTSPPlugin struct {
m7s.Plugin
UserName string `desc:"用户名"`
Password string `desc:"密码"`
UdpPort util.Range[uint16] `default:"20001-30000" desc:"媒体端口范围"` //媒体端口范围
udpPorts chan uint16
}

View File

@@ -44,7 +44,7 @@ type NetConnection struct {
// internal
auth *util.Auth
Auth *util.Auth
Conn net.Conn
keepalive int
sequence int
@@ -142,10 +142,11 @@ func (c *NetConnection) Connect(remoteURL string) (err error) {
}
c.Conn = conn
c.BufReader = util.NewBufReader(conn)
c.URL = rtspURL
c.UserAgent = "monibuca" + m7s.Version
c.Session = ""
c.auth = util.NewAuth(c.URL.User)
c.Auth = util.NewAuth(rtspURL.User)
c.URL = rtspURL
c.URL.User = nil
c.SetDescription("remoteAddr", conn.RemoteAddr().String())
c.MemoryAllocator = util.NewScalableMemoryAllocator(1 << 12)
// c.Backchannel = true
@@ -166,7 +167,7 @@ func (c *NetConnection) WriteRequest(req *util.Request) (err error) {
// https://github.com/AlexxIT/go2rtc/issues/7
req.Header["CSeq"] = []string{strconv.Itoa(c.sequence)}
c.auth.Write(req)
c.Auth.Write(req)
if c.Session != "" {
req.Header.Set("Session", c.Session)

View File

@@ -30,14 +30,14 @@ func (c *Stream) Do(req *util.Request) (*util.Response, error) {
switch res.StatusCode {
case http.StatusOK:
case http.StatusUnauthorized:
switch c.auth.Method {
switch c.Auth.Method {
case util.AuthNone:
if c.auth.ReadNone(res) {
if c.Auth.ReadNone(res) {
return c.Do(req)
}
return nil, errors.New("user/pass not provided")
case util.AuthUnknown:
if c.auth.Read(res) {
if c.Auth.Read(res) {
return c.Do(req)
}
default:

View File

@@ -23,7 +23,13 @@ o=- 0 0 IN IP4 0.0.0.0
s=-
t=0 0`
var reIN = regexp.MustCompile(`IN\s*\r\n`)
func UnmarshalSDP(rawSDP []byte) ([]*Media, error) {
// Fix IN followed by only spaces and newlines
rawSDP = reIN.ReplaceAll(rawSDP, []byte("IN IP4 0.0.0.0\r\n"))
sd := &sdp.SessionDescription{}
if err := sd.Unmarshal(rawSDP); err != nil {
// fix multiple `s=` https://github.com/AlexxIT/WebRTC/issues/417

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/textproto"
"net/url"
"regexp"
"strconv"
"strings"
@@ -18,27 +19,19 @@ type RTSPServer struct {
conf *RTSPPlugin
}
func (task *RTSPServer) Start() error {
if task.conf.UserName != "" && task.conf.Password != "" {
task.Auth = util.NewAuth(url.UserPassword(task.conf.UserName, task.conf.Password))
}
return nil
}
func (task *RTSPServer) Go() (err error) {
var receiver *Receiver
var sender *Sender
var req *util.Request
var sendMode bool
// 添加延迟函数在方法结束时清理资源
defer func() {
if receiver != nil {
receiver.Dispose()
}
if sender != nil {
sender.Dispose()
}
// 确保任何残留资源被清理
if task.NetConnection != nil {
task.NetConnection.Dispose()
}
task.Info("RTSP connection closed and resources cleaned up")
}()
for {
req, err = task.ReadRequest()
if err != nil {
@@ -50,20 +43,21 @@ func (task *RTSPServer) Go() (err error) {
task.Logger = task.Logger.With("url", task.URL.String())
task.UserAgent = req.Header.Get("User-Agent")
task.Info("connect", "userAgent", task.UserAgent)
if task.Auth != nil && req.Method != MethodOptions {
if !task.Auth.Validate(req) {
task.WriteResponse(&util.Response{
StatusCode: 401,
Status: "Unauthorized",
Header: textproto.MIMEHeader{
"Www-Authenticate": {`Basic realm="monibuca"`},
},
Request: req,
})
return
}
}
}
//if !c.auth.Validate(req) {
// res := &tcp.Response{
// Status: "401 Unauthorized",
// Header: map[string][]string{"Www-Authenticate": {`Basic realm="go2rtc"`}},
// Request: req,
// }
// if err = c.WriteResponse(res); err != nil {
// return err
// }
// continue
//}
// Receiver: OPTIONS > DESCRIBE > SETUP... > PLAY > TEARDOWN
// Sender: OPTIONS > ANNOUNCE > SETUP... > RECORD > TEARDOWN
switch req.Method {
@@ -91,6 +85,7 @@ func (task *RTSPServer) Go() (err error) {
}
receiver = &Receiver{}
task.Using(receiver.Dispose)
receiver.NetConnection = task.NetConnection
if receiver.Publisher, err = task.conf.Publish(task, strings.TrimPrefix(task.URL.Path, "/")); err != nil {
receiver = nil
@@ -111,6 +106,7 @@ func (task *RTSPServer) Go() (err error) {
case MethodDescribe:
sendMode = true
sender = &Sender{}
task.Using(sender.Dispose)
sender.NetConnection = task.NetConnection
rawQuery := req.URL.RawQuery
streamPath := strings.TrimPrefix(task.URL.Path, "/")