diff --git a/plugin/rtsp/BAD_DEVICE.md b/plugin/rtsp/BAD_DEVICE.md new file mode 100644 index 0000000..36acbc7 --- /dev/null +++ b/plugin/rtsp/BAD_DEVICE.md @@ -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 +``` \ No newline at end of file diff --git a/plugin/rtsp/index.go b/plugin/rtsp/index.go index d3e03fd..a447146 100644 --- a/plugin/rtsp/index.go +++ b/plugin/rtsp/index.go @@ -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 } diff --git a/plugin/rtsp/pkg/connection.go b/plugin/rtsp/pkg/connection.go index 2e60f3a..1c1e12c 100644 --- a/plugin/rtsp/pkg/connection.go +++ b/plugin/rtsp/pkg/connection.go @@ -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) diff --git a/plugin/rtsp/pkg/net-stream.go b/plugin/rtsp/pkg/net-stream.go index 7c01676..12b7025 100644 --- a/plugin/rtsp/pkg/net-stream.go +++ b/plugin/rtsp/pkg/net-stream.go @@ -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: diff --git a/plugin/rtsp/pkg/sdp.go b/plugin/rtsp/pkg/sdp.go index 7efe7c2..b0c4d4c 100644 --- a/plugin/rtsp/pkg/sdp.go +++ b/plugin/rtsp/pkg/sdp.go @@ -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 diff --git a/plugin/rtsp/server.go b/plugin/rtsp/server.go index 9563bf2..3922e6b 100644 --- a/plugin/rtsp/server.go +++ b/plugin/rtsp/server.go @@ -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, "/")