179 lines
7.1 KiB
Markdown
179 lines
7.1 KiB
Markdown
# 2024/12/12 日报
|
||
|
||
### 做了什么
|
||
|
||
- **wvp平台环境配置和部署**
|
||
|
||
- **Rtsp鉴权逻辑**
|
||
|
||
- 带账号和密码rtsp://admin:a1234567@192.168.1.173:554/Streaming/Channels/101
|
||
|
||
- 首先先去解析对应的地址拿到rtsp端口号和ip地址
|
||
|
||
reqURL, err := url.Parse(rtspURL)
|
||
|
||
- 判断如果是不带端口号的 加上默认地址
|
||
|
||
if reqURL.Port() == "" {
|
||
|
||
reqURL.Host = reqURL.Host + ":554" // 默认 RTSP 端口
|
||
|
||
}
|
||
|
||
- 再去连接tcp我们再去构建对应的Describe请求固定格式
|
||
|
||
**"DESCRIBE %s RTSP/1.0\r\nAccept: application/sdp\r\nCSeq: 2\r\nUser-Agent: EasyNVR\r\n\r\n"**
|
||
|
||
这里%s一般是url地址(rtsp://admin:a1234567@192.168.1.173:554/Streaming/Channels/101)可**以是不带账号密码的 也可以是带账号密码的**,我们无法控制用户是使用带密码的还是不带密码的所以我们统一直接进行rtsp直接进行鉴权,这样做的好处:
|
||
|
||
- 简化代码逻辑 如果我们要出做对**带账号密码**和**不带账号密码**的区分无疑 增加逻辑去判断这样是很复杂的
|
||
- 我们不需要判断用户用的哪一种,因为带账号密码的 也需要对方响应的**realm和nonce**再去用md5去做统一的加密再重新请求所以本质上**不带账号密码**是**一次请求** ,**带**是**两次请求** 所以只需要一个函数就能直接都判断了, 不带的账号请求成功**直接返回**,如果没返回就会用**对方响应的数据做二次请求**
|
||
|
||
- 我们如何去接收对应的数据?
|
||
|
||
- 我们要明白rtsp是一种协议如果不使用库的话我们就是对tcp传过来的数据流做处理并且是**保持连接的状态**
|
||
|
||
- 那我们就有两种方法
|
||
|
||
- 一种是固定读取一次读取固定的字节
|
||
- 一种是按具体划分读取例如“\n”
|
||
|
||
**这两种方法在tcp流中读取都有可能出现阻塞现象因为我们不知道具体的数据到底有多少**
|
||
|
||
- 如何解决呢?
|
||
|
||
- 比较粗暴一点的就是直接设置对应的读取**超时机制**
|
||
- 或者是只**读取对应次数的数据**
|
||
|
||
这两种都是比较好能解决的方法,因为本质上我们只需要保证数据读取不被阻塞就行能正常推出就行
|
||
|
||
- 为什么要用循环去读取响应
|
||
|
||
- 我们要清楚一点就是我们读取的是对方返回的**realm nonce**但是我们不知道具体的位置可能在**第三行也可能在第四行**,**这个是我们不能控制的因为协议是没有具体规定哪个字段出现在哪里**,就和HTTP协议一样字段位置你可以自定义位置,所以我们要去找到对应的位置再能拿出对应的需要的数据
|
||
|
||
- **总体思路 :先去describ访问一次 ,如果需要账号密码,会用相应的数据进行MD5的加密 ,再去访问,如果不需要就直接能访问成功**
|
||
|
||
- ```go
|
||
func GetDescribeStatus(rtspURL string) (bool, error) {
|
||
// 解析 URL,提取主机和端口
|
||
reqURL, err := url.Parse(rtspURL)
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to parse RTSP URL: %v", err)
|
||
}
|
||
var isAuthRequired bool
|
||
username := reqURL.User.Username()
|
||
password, isAuthRequired := reqURL.User.Password()
|
||
_ = isAuthRequired
|
||
parts := strings.Split(rtspURL, "//")
|
||
if len(parts) != 2 {
|
||
return false, fmt.Errorf("invalid RTSP URL: %s", rtspURL)
|
||
}
|
||
if reqURL.Port() == "" {
|
||
reqURL.Host = reqURL.Host + ":554" // 默认 RTSP 端口
|
||
}
|
||
|
||
// 连接 RTSP 服务
|
||
conn, err := net.DialTimeout("tcp", reqURL.Host, 2*time.Second)
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to connect to RTSP server: %v", err)
|
||
}
|
||
defer conn.Close()
|
||
// 构造 DESCRIBE 请求
|
||
describeRequest := fmt.Sprintf("DESCRIBE %s RTSP/1.0\r\nAccept: application/sdp\r\nCSeq: 2\r\nUser-Agent: EasyNVR\r\n\r\n", reqURL)
|
||
_, err = conn.Write([]byte(describeRequest))
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to send OPTIONS request: %v", err)
|
||
}
|
||
reader := bufio.NewReader(conn)
|
||
response, err := reader.ReadString('\n')
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to read response: %v", err)
|
||
}
|
||
// 简单解析响应是否包含 RTSP/1.0 200 OK
|
||
if strings.Contains(response, "RTSP/1.0 200 OK") {
|
||
slog.Debug("RTSP server is running and accessible.")
|
||
return true, nil
|
||
}
|
||
slog.Debug("RTSP server response indicates an issue.")
|
||
// 读取超时
|
||
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||
// 处理二次请求
|
||
var nonce string
|
||
var realm string
|
||
var authentication string
|
||
for {
|
||
authentication, err = reader.ReadString('\n')
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to read response: %v", err)
|
||
}
|
||
parts = strings.Split(authentication, `WWW-Authenticate: Digest`)
|
||
if len(parts) > 1 {
|
||
break
|
||
}
|
||
}
|
||
parts = strings.Split(authentication, `realm="`)
|
||
if len(parts) > 1 {
|
||
realm = strings.Split(parts[1], `"`)[0]
|
||
}
|
||
// 提取nonce
|
||
parts = strings.Split(authentication, `nonce="`)
|
||
if len(parts) > 1 {
|
||
nonce = strings.Split(parts[1], `"`)[0]
|
||
}
|
||
// 提取username
|
||
parts = strings.Split(authentication, `username="`)
|
||
if len(parts) > 1 {
|
||
username = strings.Split(parts[1], `"`)[0]
|
||
}
|
||
response = calculateResponse(username, realm, password, nonce, "DESCRIBE", rtspURL)
|
||
// 计算响应
|
||
describeRequest = fmt.Sprintf("DESCRIBE %s RTSP/1.0\r\nAccept: application/sdp\r\nCSeq: 3\r\nUser-Agent: EasyNVR\r\nAuthorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\" \r\n\r\n", reqURL, username, realm, nonce, reqURL, response)
|
||
_, err = conn.Write([]byte(describeRequest))
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to send OPTIONS request: %v", err)
|
||
}
|
||
reader = bufio.NewReader(conn)
|
||
response, err = reader.ReadString('\n')
|
||
if err != nil {
|
||
return false, fmt.Errorf("failed to read response: %v", err)
|
||
}
|
||
// 简单解析响应是否包含 RTSP/1.0 200 OK
|
||
if strings.Contains(response, "RTSP/1.0 200 OK") {
|
||
slog.Debug("RTSP server is running and accessible.")
|
||
return true, nil
|
||
} else {
|
||
return false, fmt.Errorf("failed response: %s", response)
|
||
}
|
||
}
|
||
func computeMD5(data string) string {
|
||
hash := md5.Sum([]byte(data))
|
||
return hex.EncodeToString(hash[:])
|
||
}
|
||
|
||
func calculateResponse(username, realm, password, nonce, publicMethod, url string) string {
|
||
// Calculate HA1
|
||
ha1Input := fmt.Sprintf("%s:%s:%s", username, realm, password)
|
||
ha1 := computeMD5(ha1Input)
|
||
|
||
// Calculate HA2
|
||
ha2Input := fmt.Sprintf("%s:%s", publicMethod, url)
|
||
ha2 := computeMD5(ha2Input)
|
||
|
||
// Calculate response
|
||
responseInput := fmt.Sprintf("%s:%s:%s", ha1, nonce, ha2)
|
||
response := computeMD5(responseInput)
|
||
|
||
// Output the calculated response
|
||
return response
|
||
}
|
||
```
|
||
|
||
|
||
### 学到了什么
|
||
|
||
- **tcp连接超时**
|
||
|
||
- ```go
|
||
conn.SetReadDeadline(time.Now().Add(5 * time.Second)) // 设置超时
|
||
```
|