国标28181:接收设备注册

1059 篇文章 285 订阅

工具

自从国标28181推出以来,国家安防行业一直在主推这个标准协议。刚开始确实有不少阻力,比如很多厂家还采用私有协议或是ONVIF协议作为主要的对接协议。这样很大的阻碍了安防行业的互联互通,虽然GB28181的推出刚出来是有些不足(比如缺少很多的控制信令标准),这个在2014补充版及2016正式版(替代2011版)已经基本完善了。

GB28181最大的优势在于采用了SIP协议作为通信信令,RTP协议作为流媒体传送协议。这样基本上与国际标准协议接轨,极大的促进了安防行业的资源整合。比如以前的摄像头都是零散的,每个公安局只能看到自己小范围的监控录像,发生了重大案件,还需要把硬盘监控资料交到上级部门,上级部门再组织大量人员人工回放查看,这样太影响效率了!不能更快的把案情处理,那天网工程投入这么多银子不打水漂了。公安部门强制在招标文件中规定要求支持28181,所以后面越来越多的设备,平台都把GB28181协议作为主要的互联协议。促进了GB28181的发展以及整个安防行业的发展。

现在越来越多的大企业加入了安防,阿里、华为、腾讯这种超大公司也都有自己的28181平台,阿里云还在做支持国标28181的设备协议接入。下面关于是如何在终端实现GB28181的设计思路:

在这里插入图片描述

开发身份:要分设备端(IPC、NVR、平台服务器)

自动化测试工具

怎么用:

需要同时打开GB28181-2016自动化测试工具\GB28181Test.exeGB28181-2016模拟设备\GB28181Device.exe

  • GB28181-2016模拟设备:模拟了一个IPC客户端
  • GB28181-2016自动化测试工具:是GB28181服务端。
    注意GB28181Test.exe界面上配置的,和config.xml文件中填写的必须一致

在这里插入图片描述

域名配置:

  • sipID:44010200492000000001
  • sip域:4401020049
  • sipID:34020000002000000001
  • sip域:3402000000

实现注册功能

理论

流程如下:

下级平台,向上级平台(SIP服务器)发起注册

  • Step.1: 注册设备对方中心服务器发送 Register 注册消息
  • Step.2: 中心服务器检查注册设备带来信令中的 Authorization 字段(鉴权字段),发现 Register 信令中未带鉴权字段。回复注册设备: 401 Unauthorized(注册未带鉴权)。注意,这不是异常报错,这是国标注册中的正常流程。
  • Step.3:注册设备重新向中心服务器发送 Register 注册消息,并带上鉴权字段(Register With Authorized)
  • Step.4:中心服务器检查 Authorization 字段,如果该鉴权通过,则回复 200OK,设备在线。
  1. 下级平台(注册设备)向上级平台(服务器)注册, 下级平台需要知道上级平台 sipid、ip、端口号,这样就可以向上级平台注册了。
  2. 上级平台收到下级平台注册消息后, 需要构造sipto, sipfrom, sip contact 这些header, 这些值怎么取?, 其实这些值就在客户请求过的 eXosip_event_t 对象里面

抓包

注意,下面不是同一个抓包流程,仅仅分析怎么填写

第零步:启动服务端

启动CG28181 服务端,也即 SIP Server,这正是我们要实现的

第一步:【设备>>服务端】请求注册

摄像机端发送Register消息,如果服务端不应答,摄像机端会一直发送直到收到服务端应答为止。如果服务器端重新运行,需要手动再次开启设备。

REGISTER sip:440102004921111111111@4401020049 SIP/2.0
Via: SIP/2.0/TCP 192.168.0.213:51762;rport;branch=z9hG4bK2144142982
From: <sip:44010200492222222221@4401020049>;tag=1054224337
To: <sip:44010200492222222221@4401020049>
Call-ID: 1507740566
CSeq: 1 REGISTER
Contact: <sip:44010200492222222221@192.168.0.213:33727>
Max-Forwards: 70
User-Agent: IP Camera
Expires: 3600
Content-Length: 0

看下起始行:

REGISTER sip:440102004921111111111@4401020049 SIP/2.0
  • 表示设备向域名为4401020049的服务器发送注册,SIP版本号为2.0
    • 440102004921111111111为SIP服务器ID
    • 4401020049 为SIP服务器域,这里也可以填写SIP服务器的地址,类似92.168.10.177:5060
Via: SIP/2.0/TCP 192.168.0.213:51762;rport;branch=z9hG4bK2144142982
  • 192.168.0.213:51762:为摄像头IP和端口
From: <sip:44010200492222222221@4401020049>;tag=1054224337
  • 表示这个请求是哪个设备发起的。意思是该设备是由域名为4401020049的服务器控制的,它将向4401020049发送一个注册请求。
    • 44010200492222222221:设备ID,也叫做 SIP用户名,即设备的ID44010200492222222221,用来标识当前设备的省份
    • 4401020049:SIP服务器域名,也可以写成IP格式,比如92.168.10.177:5060
To: <sip:44010200492222222221@4401020049>

请求起始行:表示UAC向IP地址为192.168.10.177的服务器发起注册,SIP版本号为2.0

From字段:指明该REGISTER请求消息由UAS(IP地址:192.168.10.177)控制的UAC发起的。

To字段:指明REGISTER请求接收方的地址。此时REGISTER请求的接收方为IP地址为192.168.10.177的UAS。(这个值和To头域的值相同,除非这个请求是第三方发起的注册请求。)
Call-ID字段:UAC发出的给某个注册服务器(registrar)的所有注册请求都应该有相同的Call-ID头域值。如果相同的客户端用了不同的Call-ID值,注册服务器(registrar)就不能检测是否一个REGISTER请求由于延时的关系导致了故障。
Cseq字段:Cseq值保证了REGISTER请求的正确顺序。一个UA为每一个具备相同的Call-ID的REGISTER请求顺序递增这个Cseq字段。
Contact字段:在REGISTER请求中的Contact字段指明用户可达位置。
Expires字段:表示该登记生存期为3600s。

  • Content-Length字段:表明此请求消息消息体的长度为空,即此消息不带会话描述。

第二步:【服务端>>设备】要求鉴权

SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.0.107:55204;branch=z9hG4bKc1b6d
From: <sip:34020000001110000001@4401020049>;tag=4e3e
To: <sip:34020000001110000001@4401020049>;tag=1724123124
Call-ID: 00002F3F00007AEF@192.168.0.107
CSeq: 1 REGISTER
WWW-Authenticate: Digest realm="34020000", nonce="awer23sdfj123123", opaque="c3a02f1ecb122d255c4ae2266129d044", algorithm=MD5
User-Agent: General
Content-Length: 0




第三步:【设备>>服务端】携带鉴权信息

REGISTER sip:sip:44010200492000000001@192.168.0.60:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.0.107:55204;branch=z9hG4bKc1bda
From: <sip:34020000001110000001@4401020049>;tag=4e3e
To: <sip:34020000001110000001@4401020049>
Contact: <sip:34020000001110000001@192.168.0.107:55204>
Call-ID: 00002F3F00007AEF@192.168.0.107
CSeq: 2 REGISTER
Max-Forwards: 70
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: HTSIP AGENT 2.0
Authorization: Digest username="34020000001110000001",realm="34020000",uri="sip:192.168.0.60",nonce="awer23sdfj123123",response="aeb71cf5e03ec6ae60b82e7f013357fa",algorithm=MD5
Content-Length: 0



(2)第四步:【服务端>>设备】,回应鉴权成功

SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.0.107:55204;branch=z9hG4bKc1bda
From: <sip:34020000001110000001@4401020049>;tag=4e3e
To: <sip:34020000001110000001@4401020049>;tag=31243r3412
Call-ID: 00002F3F00007AEF@192.168.0.107
CSeq: 2 REGISTER
User-Agent: General
Date: 2022-05-19T21:19:29
Expires: 300
Content-Length: 0


实践

IPC客户端发送注册请求

运行[GB28181-2016模拟设备]。注意填好IP地址(下面之所以填写5060,是因为代码中运行了5060)
在这里插入图片描述

我们用wireshark抓包抓到的包如下:
在这里插入图片描述
wireshark抓到的包内容:

在这里插入图片描述

REGISTER sip:sip:44010200492000000001@192.168.0.12:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.0.60:60564;branch=z9hG4bK15982d8f
From: <sip:34020000001110000001@4401020049>;tag=4989
To: <sip:34020000001110000001@4401020049>
Contact: <sip:34020000001110000001@192.168.0.60:60564>
Call-ID: 0000575C00003D94@192.168.0.60
CSeq: 1 REGISTER
Max-Forwards: 70
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: HTSIP AGENT 2.0
Content-Length: 0

怎么用代码接收上面的内容呢?

接收IPC客户端发来的信息

#include <stdlib.h>
#include <stdio.h>
#include <Common/Log.h>
#include "eXosip2/eXosip.h"
#include "osipparser2/osip_message.h"
#include "eXosip2.h"

//初始化 监听端口
int Init(int listenport)
{
    int iRet = eXosip_init();
    if (iRet != OSIP_SUCCESS)
    {
        printf("eXosip2 fail! \n");
        exit(1);
    }
    iRet = eXosip_listen_addr(IPPROTO_UDP, NULL, listenport, AF_INET, 0);
    if (iRet != OSIP_SUCCESS)
    {
        eXosip_quit();
        DBGPrint(M_SipUA, BREAK_LEVEL, "eXosip2 could not initialize transport layer! \n");
        exit(1);
    }

    DBGPrint(M_SipUA, BREAK_LEVEL, "%s: SipSvr Listen Port:%d Sucess!!!!", __FUNCTION__ , listenport);
    return 0;
}



int main(int argc, char *argv[]) {
    logger::Instance();
    LOG_START("", DEBUG_LEVEL, 0, "");

    //初始化exosip协议栈端口
    Init(5060);
    //等待接收sip数据
    while (1) {
        //---------------- eXosip running process ----------------//
        eXosip_execute();  //这是必须的
        eXosip_event_t *pSipEvt = eXosip_event_wait(0, 50);
        while (pSipEvt != NULL) {
            printf("-----------------\r\n");
            eXosip_lock();
            eXosip_default_action(pSipEvt);
            eXosip_unlock();
            eXosip_event_free(pSipEvt);
            pSipEvt = eXosip_event_wait(0, 50);
        }

        usleep(50000);
    }
}

运行效果

看到了服务端有反应了,
在这里插入图片描述

pSipEvt就是刚刚接收到的内容,我们打印一下:


int main(int argc, char *argv[]) {
    logger::Instance();
    LOG_START("", DEBUG_LEVEL, 0, "");

    //初始化exosip协议栈端口
    Init(5060);
    //等待接收sip数据
    while (1) {
        //---------------- eXosip running process ----------------//
        eXosip_execute();  //这是必须的
        eXosip_event_t *pSipEvt = eXosip_event_wait(0, 50);
        while (pSipEvt != NULL) {
               printf("type: %d, textinfo: %s, tid: %d, tid:%d, sip_method:  %s\r\n", pSipEvt->type, pSipEvt->textinfo, pSipEvt->tid, pSipEvt->tid, pSipEvt->request->sip_method);
            eXosip_lock();
            eXosip_default_action(pSipEvt);
            eXosip_unlock();
            eXosip_event_free(pSipEvt);
            pSipEvt = eXosip_event_wait(0, 50);
        }

        usleep(50000);
    }
}



我们可以看到,
在这里插入图片描述

对应
在这里插入图片描述
确实是REGISTER请求。

但是IPC设备会发来不同的信令,因此我们写一个ProceXsipEvt函数来处理接收的数据

int ProceXsipEvt(eXosip_event_t* pSipEvt){
    if (NULL == pSipEvt){
        DBGPrint(M_SipUA, ERROR_LEVEL, "%s: pSipEvt is null pointer!", __FUNCTION__);
        return -1;
    }

    switch (pSipEvt->type)
    {
        /* REGISTER related events */
        case EXOSIP_REGISTRATION_NEW:   // 宣布新的登记 ---> 注册事件处理
            DBGPrint(M_SipUA, BREAK_LEVEL, "(%d)注册事件处理\r\n", pSipEvt->type);
            break;
        case EXOSIP_REGISTRATION_SUCCESS :
            DBGPrint(M_SipUA, BREAK_LEVEL, "(%d)用户注册成功\r\n", pSipEvt->type);
            break;
        case EXOSIP_REGISTRATION_FAILURE  :
            DBGPrint(M_SipUA, BREAK_LEVEL, "(%d)用户注册不成功\r\n", pSipEvt->type);
            break;
        case EXOSIP_REGISTRATION_REFRESHED  :
            DBGPrint(M_SipUA, BREAK_LEVEL, "(%d)注册已刷新    \r\n", pSipEvt->type);
            break;
        case EXOSIP_REGISTRATION_TERMINATED  :
            printf("(%d)UA is not registred any more    \r\n", pSipEvt->type);
            break;
         /* INVITE related events within calls */
        case EXOSIP_CALL_INVITE  :
            printf("(%d)announce a new call    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_REINVITE  :
            printf("(%d)announce a new INVITE within call    \r\n", pSipEvt->type);
            break;

        case EXOSIP_CALL_NOANSWER  :
            printf("(%d)announce no answer within the timeout    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_PROCEEDING  :
            printf("(%d)announce processing by a remote app    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_RINGING  :
            printf("(%d)announce ringback    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_ANSWERED  :
            printf("(%d)announce start of call     \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_REDIRECTED  :
            printf("(%d)announce a redirection      \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_REQUESTFAILURE  :
            printf("(%d) announce a request failure    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_SERVERFAILURE  :
            printf("(%d) announce a server failure    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_GLOBALFAILURE  :
            printf("(%d)  announce a global failure    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_ACK  :
            printf("(%d) ACK received for 200ok to INVITE    \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_CANCELLED  :
            printf("(%d) announce that call has been cancelled   \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_TIMEOUT  :
            printf("(%d)  announce that call has failed    \r\n", pSipEvt->type);
            break;
          /* request related events within calls (except INVITE) */
        case EXOSIP_CALL_MESSAGE_NEW  :
            printf("(%d) announce new incoming request.   \r\n", pSipEvt->type);
            break;

        case EXOSIP_CALL_MESSAGE_PROCEEDING  :
            printf("(%d) announce a 1xx for request.   \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_MESSAGE_ANSWERED  :
            printf("(%d) announce a 200ok   \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_MESSAGE_REDIRECTED  :
            printf("(%d)  announce a failure.  \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_MESSAGE_REQUESTFAILURE  :
            printf("(%d)  announce a failure:  \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_MESSAGE_SERVERFAILURE  :
            printf("(%d)  announce a failure:  \r\n", pSipEvt->type);
            break;

        case EXOSIP_CALL_MESSAGE_GLOBALFAILURE  :
            printf("(%d)  announce a failure:  \r\n", pSipEvt->type);
            break;
        case EXOSIP_CALL_CLOSED  :
            printf("(%d)  a BYE was received for this call   \r\n", pSipEvt->type);
            break;
            /* for both UAS & UAC events */
        case EXOSIP_CALL_RELEASED  :
            printf("(%d) call context is cleared.     \r\n", pSipEvt->type);
            break;
            /* response received for request outside calls */
        case EXOSIP_MESSAGE_NEW  :
            printf("(%d) announce new incoming request.   \r\n", pSipEvt->type);
            break;
        case EXOSIP_MESSAGE_PROCEEDING  :
            printf("(%d) announce a 1xx for request.  \r\n", pSipEvt->type);
            break;
        case EXOSIP_MESSAGE_ANSWERED  :
            printf("(%d) announce a 200ok \r\n", pSipEvt->type);
            break;
        case EXOSIP_MESSAGE_REDIRECTED  :
            printf("(%d) announce a failure.  \r\n", pSipEvt->type);
            break;
        case EXOSIP_MESSAGE_REQUESTFAILURE  :
            printf("(%d) announce a failure.   \r\n", pSipEvt->type);
            break;
        case EXOSIP_MESSAGE_SERVERFAILURE  :
            printf("(%d) announce a failure.  \r\n", pSipEvt->type);
            break;
        case EXOSIP_MESSAGE_GLOBALFAILURE  :
            printf("(%d) announce a failure.   \r\n", pSipEvt->type);
            break;
         /* Presence and Instant Messaging */
        case EXOSIP_SUBSCRIPTION_UPDATE  :
            printf("(%d) announce incoming SUBSCRIBE.   \r\n", pSipEvt->type);
            break;
        case EXOSIP_SUBSCRIPTION_CLOSED  :
            printf("(%d) announce end of subscription.   \r\n", pSipEvt->type);
            break;
        case EXOSIP_SUBSCRIPTION_NOANSWER  :
            printf("(%d) announce no answer      \r\n", pSipEvt->type);
            break;
        case EXOSIP_SUBSCRIPTION_PROCEEDING  :
            printf("(%d) announce a 1xx      \r\n", pSipEvt->type);
            break;
        case EXOSIP_SUBSCRIPTION_ANSWERED  :
            printf("(%d)  announce a 200ok        \r\n", pSipEvt->type);
            break;
        case EXOSIP_SUBSCRIPTION_REDIRECTED  :
            printf("(%d)  announce a redirection        \r\n", pSipEvt->type);
            break;
        case EXOSIP_SUBSCRIPTION_REQUESTFAILURE  :
            printf("(%d) announce a request failure      \r\n", pSipEvt->type);
            break;

        case EXOSIP_SUBSCRIPTION_SERVERFAILURE  :
            printf("(%d)  announce a server failure        \r\n", pSipEvt->type);
            break;

        case EXOSIP_SUBSCRIPTION_GLOBALFAILURE  :
            printf("(%d) announce a global failure    \r\n", pSipEvt->type);
            break;
        case EXOSIP_SUBSCRIPTION_NOTIFY  :
            printf("(%d)   announce new NOTIFY request     \r\n", pSipEvt->type);
            break;

        case EXOSIP_SUBSCRIPTION_RELEASED  :
            printf("(%d)  call context is cleared.   \r\n", pSipEvt->type);
            break;

        case EXOSIP_IN_SUBSCRIPTION_NEW  :
            printf("(%d)   announce new incoming SUBSCRIBE \r\n", pSipEvt->type);
            break;

        case EXOSIP_IN_SUBSCRIPTION_RELEASED  :
            printf("(%d)  announce end of subscription.   \r\n", pSipEvt->type);
            break;

        case EXOSIP_NOTIFICATION_NOANSWER  :
            printf("(%d)  announce no answer  \r\n", pSipEvt->type);
            break;

        case EXOSIP_NOTIFICATION_PROCEEDING  :
            printf("(%d)   announce a 1xx      .   \r\n", pSipEvt->type);
            break;

        case EXOSIP_NOTIFICATION_ANSWERED  :
            printf("(%d)  announce a 200ok   .   \r\n", pSipEvt->type);
            break;

        case EXOSIP_NOTIFICATION_REDIRECTED  :
            printf("(%d)  announce a redirection       .   \r\n", pSipEvt->type);
            break;
        case EXOSIP_NOTIFICATION_REQUESTFAILURE  :
            printf("(%d)  announce a request failure   .   \r\n", pSipEvt->type);
            break;
        case EXOSIP_NOTIFICATION_SERVERFAILURE  :
            printf("(%d)  announce a server failure   .   \r\n", pSipEvt->type);
            break;
        case EXOSIP_NOTIFICATION_GLOBALFAILURE  :
            printf("(%d)  announce a global failure  .   \r\n", pSipEvt->type);
            break;
        case EXOSIP_EVENT_COUNT  :
            printf("(%d)  MAX number of events    .   \r\n", pSipEvt->type);
            break;



        default:
            printf( "(%d)  未处理消息 \r\n ", pSipEvt->type);

            break;
    }

    return 0;
}

下面我们要做的就是根据IPC客户端发来的不同类型的信令,然后做出回应。

回应第一个REGISTER请求,要求其鉴权

第一步:检查 Authorization 字段字段

根据上面的流程,我们应该检查 Authorization 字段,看是否有鉴权信息,怎么检测有没有这个字段呢?用osip_message_get_authorization接口,它可以检测header中有没有这个字段,如果没有,就返回-1:

/**
 * Get one Authorization header.
 * @param sip The element to work on.
 * @param pos The index of the element to get.
 * @param dest A pointer on the header found.
 */
	int osip_message_get_authorization(const osip_message_t * sip, int pos,
									   osip_authorization_t ** dest);

代码如下:

int ProceXsipEvt(eXosip_event_t* pSipEvt){
    if (NULL == pSipEvt){
        DBGPrint(M_SipUA, ERROR_LEVEL, "%s: pSipEvt is null pointer!", __FUNCTION__);
        return -1;
    }

    printf("type: %d, textinfo: %s, tid: %d, tid:%d, sip_method:  %s\r\n", pSipEvt->type, pSipEvt->textinfo, pSipEvt->tid, pSipEvt->tid, pSipEvt->request->sip_method);

    int iRet = -1;
    osip_authorization_t *pWWWAuEcho = NULL;
    iRet = osip_message_get_authorization(pSipEvt->request, 0, &pWWWAuEcho);
    if(pWWWAuEcho == NULL){
        printf("摄像机第一次发来未鉴权注册信息,返回401  %d\r\n" , iRet);  // 摄像机第一次发来未鉴权注册信息,返回401
    }else{
        printf(" 摄像机第二次发来鉴权注册信息,返回200 OK %d\r\n" , iRet);
    }


    return 0;
}

打印出了这个。
在这里插入图片描述

第二步:向IPC客户端,回复401 Unauthorized无权限,要求进行用户认证,并在消息包头添加如下字段:

 WWW-Authenticate: Digest realm="3402000000", nonce="a321cfdd39ff6233"

其中realm指的是域名(随意根据自己项目的实际情况填写),Nonce[点击查看]为以随机数;

代码实现如下:

char *pcRealm = "640001";
char *pcNonce = "6fe9ba44a76be22a";
static void Register401Unauthorized(eXosip_event_t* pSipEvt)
{
    int iReturnCode = 0;
    osip_message_t * pSRegister = NULL;

    osip_www_authenticate_t * header = NULL;
    osip_www_authenticate_init(&header);

    osip_www_authenticate_set_auth_type (header,osip_strdup("Digest"));
    osip_www_authenticate_set_realm(header,osip_enquote(pcRealm));
    osip_www_authenticate_set_nonce(header,osip_enquote(pcNonce));

    char *pDest = NULL;
    osip_www_authenticate_to_str(header,&pDest);
    
    iReturnCode = eXosip_message_build_answer(pSipEvt->tid,SIP_UNAUTHORIZED,&pSRegister);
    if ( iReturnCode == 0 && pSRegister != NULL )
    {
        osip_message_set_www_authenticate(pSRegister,pDest);
        eXosip_message_send_answer(pSipEvt->tid,SIP_UNAUTHORIZED,pSRegister);
    }

    osip_www_authenticate_free(header);
    osip_free(pDest);
}

int ProceXsipEvt(eXosip_event_t* pSipEvt){
    if (NULL == pSipEvt){
        DBGPrint(M_SipUA, ERROR_LEVEL, "%s: pSipEvt is null pointer!", __FUNCTION__);
        return -1;
    }

    printf("type: %d, textinfo: %s, tid: %d, tid:%d, sip_method:  %s\r\n", pSipEvt->type, pSipEvt->textinfo, pSipEvt->tid, pSipEvt->tid, pSipEvt->request->sip_method);

    int iRet = -1;
    osip_authorization_t *pWWWAuEcho = NULL;
    iRet = osip_message_get_authorization(pSipEvt->request, 0, &pWWWAuEcho);
    if(pWWWAuEcho == NULL){
        printf("摄像机第一次发来未鉴权注册信息,返回401  %d\r\n" , iRet);  // 摄像机第一次发来未鉴权注册信息,返回401
        Register401Unauthorized(pSipEvt);
    }else{
        printf(" 摄像机第二次发来鉴权注册信息,返回200 OK %d\r\n" , iRet);
    }


    return 0;
}

效果:

在这里插入图片描述

抓包抓到的内容如下:

SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.0.60:62066;branch=z9hG4bK15ab93de
From: <sip:34020000001110000001@4401020049>;tag=5a25
To: <sip:34020000001110000001@4401020049>;tag=501755099
Call-ID: 00003F6C0000474D@192.168.0.60
CSeq: 1 REGISTER
WWW-Authenticate: Digest realm="640001", nonce="6fe9ba44a76be22a"
User-Agent: eXosip/3.3.0
Content-Length: 0

可以看出:

  • 401 Unauthorized(无权限)响应
  • 并且通过WWW-Authenticate字段携带UAS支持的认证方式,产生本次认证的nonce

UAC会重新发送了一个请求:

IPC客户端会重新发送一个请求,抓包如下:

ZREGISTER sip:sip:44010200492000000001@192.168.0.12:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.0.60:61926;branch=z9hG4bK233cb42b
From: <sip:34020000001110000001@4401020049>;tag=31a1
To: <sip:34020000001110000001@4401020049>
Contact: <sip:34020000001110000001@192.168.0.60:61926>
Call-ID: 00000D4F00003828@192.168.0.60
CSeq: 2 REGISTER
Max-Forwards: 70
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: HTSIP AGENT 2.0
Authorization: Digest username="34020000001110000001",realm="640001",uri="sip:192.168.0.12",nonce="6fe9ba44a76be22a",response="a0a200e89e34dc33aaa9dc2347541cf0",algorithm=MD5
Content-Length: 0

可以看到,UAC重新向UAS发起注册请求,携带WWW-Authorization字段:

Authorization: Digest username="34020000001110000001",realm="640001",uri="sip:192.168.0.12",nonce="6fe9ba44a76be22a",response="a0a200e89e34dc33aaa9dc2347541cf0",algorithm=MD5

另外,还需要注意的是由一个Expires,当其值为0时表示注销。

Expires: 3600

回应第二个REGISTER请求

  • 如果鉴权成功, 回复200
  • 如果鉴权密码错误,回复403
  • 如果pRegisterMsg->from->url->username也就是设备ID在数据库中找不到,回应404
    //鉴权, 鉴权成功回复200OK
    if (MD5AuthValidate(pSipEvt->request->sip_method, pWWWAuEcho, auth_username, auth_passwd)) {
        //鉴权成功,回复200成功
        //添加用户信息
        SendRegisterAnswer(pSipEvt->tid, SIP_OK, true);
    } else {
        //鉴权失败,回复403
        SendRegisterAnswer(pSipEvt->tid, SIP_FORBIDDEN, false);
    }

回应403

static void RegisterValidate(eXosip_event_t* pSipEvt) {
    osip_message_t *pRegisterMsg = NULL;
    pRegisterMsg = pSipEvt->request;

    printf("device = %s, passwd = %s, sip_method = %s, tid = %d\r\n, ",
           pRegisterMsg->from->url->username, pRegisterMsg->from->url->password,
           pRegisterMsg->sip_method, pSipEvt->tid);

    int tid = pSipEvt->tid, code = 403;
    osip_message_t * pMsgAnswer = NULL;
    eXosip_message_build_answer(pSipEvt->tid, code,&pMsgAnswer);
    eXosip_message_send_answer(tid, code, pMsgAnswer);
    return;
}

抓包如下:

SIP/2.0 403 Forbidden
Via: SIP/2.0/UDP 192.168.0.60:57108;branch=z9hG4bK2372eeec
From: <sip:34020000001110000001@4401020049>;tag=5ef7
To: <sip:34020000001110000001@4401020049>;tag=1746130768
Call-ID: 0000524000001A44@192.168.0.60
CSeq: 2 REGISTER
User-Agent: eXosip/3.3.0
Content-Length: 0

回复200

疑问:REGISTER认证加密计算?

计算Response过程:

  • 1)下载MD5加解密工具。
  • 2)将username,realm,password依次组合获取1个字符串,并对这个字符串使用算法H来进行加密,获得密文1.
  • 3)将method,uri依次组合获取1个字符串,并对这个字符串使用算法H来进行加密,获得密文2.
  • 4)将密文1,nonce和密文2依次组合获取1个字符串,并对这个字符串使用算法H来进行加密,获得密文3.

这个密文3就是最终的结果Response。

步骤1.

32028104561321000011:32028100002000000000:admin12345
MD5加密后密文 :
c9504793987d578e0640c26357fb1097

步骤2:
REGISTER:sip:32028100002000000000@3202810000
MD5加密后密文 :
3e9f8eefefb80928175d2f3671f9b5e9

步骤3:

密文1+nonce+密文2:
c9504793987d578e0640c26357fb1097:519c58b9519c58b9ac8c58b9a79c58b9d39c58b9009c58b9ac8c58b9198c58:3e9f8eefefb80928175d2f3671f9b5e9
MD5加密后密文:
c990b63bfa21138bd724a467ec27b134
response = c990b63bfa21138bd724a467ec27b134

MD5加密后密文 = response 所以注册校验成功。


bool MD5AuthValidate(char *pMethod, osip_authorization_t *pAuthEcho, char *auth_username, char *auth_passwd){
    return true;
}

int SendMessageAnswer(int Tid, int Code, bool bSetTime, int Expires, TraverseAddr_T *pTvsAddr)
{
    osip_message_t * pMsgAnswer = NULL;
    int ret = eXosip_message_build_answer(Tid, Code,&pMsgAnswer);
    if(ret == OSIP_SUCCESS && pMsgAnswer != NULL )
    {
#define CLIP_BUFFER_SIZE               256
        if (bSetTime == true)
        {
            char TmpBuf[CLIP_BUFFER_SIZE+1] = {0};
            snprintf(TmpBuf, CLIP_BUFFER_SIZE, "%d", (int)Expires);
            osip_message_set_expires(pMsgAnswer, TmpBuf);

            osip_header_t*  pHeader = NULL;
            //char            TmpBuf[CLIP_BUFFER_SIZE+1];
            //initialize Date header
            int iRet = osip_header_init(&pHeader);
            if (OSIP_SUCCESS == iRet)
            {
                time_t now;
                struct timeval tv;
                //Get current time
                gettimeofday(&tv,NULL);

                now = tv.tv_sec;
                //			time(&now);
                //struct tm* pTmVal = localtime(&now);
                struct tm STm = {0};
                struct tm* pTmVal = localtime_r(&now, &STm);  //使用线程安全函数
                if (pTmVal != NULL)
                {
                    //系统启用夏令时,需要减去一个小时。
                    if (1 == pTmVal->tm_isdst)
                    {
                        now -= 3600;
                        //pTmVal = localtime(&now);
                        pTmVal = localtime_r(&now, &STm);  //使用线程安全函数
                        if (NULL == pTmVal)
                            return -1;
                    }

                    //2010-07-26 16:05:10
                    snprintf(TmpBuf, CLIP_BUFFER_SIZE, "%4d-%02d-%02dT%02d:%02d:%02d.%03d", pTmVal->tm_year+1900, pTmVal->tm_mon+1, pTmVal->tm_mday, pTmVal->tm_hour, \
						pTmVal->tm_min, pTmVal->tm_sec,(int)(tv.tv_usec/1000));

                    //set Date field
                    osip_header_set_name(pHeader, osip_strdup("Date") );
                    osip_header_set_value(pHeader, osip_strdup(TmpBuf) );
                    osip_list_add(&pMsgAnswer->headers, pHeader, -1);
                }
            }
#define USERNAME_MAX_LEN               256
#define DOMAIN_MAX_LEN                 64
#define PASSWD_MAX_LEN                 64
#define SIP_AOR_MAX_SIZE               (USERNAME_MAX_LEN + DOMAIN_MAX_LEN)
            if (pTvsAddr != NULL)
            {
                osip_header_t*  pHeader2 = NULL;
                iRet = osip_header_init(&pHeader2);
                if (OSIP_SUCCESS == iRet)
                {
                    char Contact[SIP_AOR_MAX_SIZE + 1] = {0};
                    snprintf(Contact, SIP_AOR_MAX_SIZE, "sip:%s@%s:%d", pMsgAnswer->to->url->username, pTvsAddr->OutIpAddr, pTvsAddr->OutPort);
                    osip_header_set_name(pHeader2, osip_strdup("Contact") );
                    osip_header_set_value(pHeader2, osip_strdup(Contact) );
                    osip_list_add(&pMsgAnswer->headers, pHeader2, -1);
                }
            }
        }
        eXosip_message_send_answer(Tid, Code, pMsgAnswer);
    }
    else
    {
        //DBGPrint(M_SipUA, ERROR_LEVEL, "%s:Build Message Answer Failed Tid<%d> Code<%d>, ret:%d!", __FUNCTION__, Tid, Code, ret);
    }
    return 0;
}


int ProceXsipEvt(eXosip_event_t* pSipEvt){
    if (NULL == pSipEvt){
        DBGPrint(M_SipUA, ERROR_LEVEL, "%s: pSipEvt is null pointer!", __FUNCTION__);
        return -1;
    }

    printf("type: %d, textinfo: %s, tid: %d, tid:%d, sip_method:  %s\r\n", pSipEvt->type, pSipEvt->textinfo, pSipEvt->tid, pSipEvt->tid, pSipEvt->request->sip_method);

    int iRet = -1;
    osip_authorization_t *pWWWAuEcho = NULL;
    iRet = osip_message_get_authorization(pSipEvt->request, 0, &pWWWAuEcho);
    if(pWWWAuEcho == NULL){
        printf("摄像机第一次发来未鉴权注册信息,返回401  %d\r\n" , iRet);  // 摄像机第一次发来未鉴权注册信息,返回401
        Register401Unauthorized(pSipEvt);
    }else{
        printf(" 摄像机第二次发来鉴权注册信息,返回200 OK %d\r\n" , iRet);



        if (MD5AuthValidate(pSipEvt->request->sip_method, pWWWAuEcho,  "34020000001110000001", "12345678a")){
            osip_message_t* pRegisterMsg = NULL;
            pRegisterMsg = pSipEvt->request;
            osip_header_t*        p_header = NULL;
            int                expires = -1;
            osip_message_get_expires(pRegisterMsg, 0, &p_header);
            if (p_header != NULL)
            {
                if (p_header->hvalue != NULL)
                {
                    expires = atoi(p_header->hvalue);
                }
            }else{
                printf("fatal");
                exit(0);
            }
            if (expires <= 0){
                printf("bbbbbbbbbbbbbbbbb");
            }else{
                SendMessageAnswer(pSipEvt->tid, SIP_OK, true, expires, &pRegisterMsg->TvsAddr);
            }

        }
    }
    return 0;
}


抓包如下:

SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.0.60:54392;branch=z9hG4bK23bc2f3d
From: <sip:34020000001110000001@4401020049>;tag=1c35
To: <sip:34020000001110000001@4401020049>;tag=1789518916
Call-ID: 00000140000000E0@192.168.0.60
CSeq: 2 REGISTER
User-Agent: eXosip/3.3.0
Expires: 3600
Date: 2022-05-16T11:24:11.309
Contact: sip:34020000001110000001@192.168.0.60:54392
Content-Length: 0

小结



bool MD5AuthValidate(char *pMethod, osip_authorization_t *pAuthEcho, char *auth_username, char *auth_passwd){
    return true;
}
char *pcRealm = "640001";
char *pcNonce = "6fe9ba44a76be22a";
static void Register401Unauthorized(eXosip_event_t* pSipEvt)
{
    int iReturnCode = 0;
    osip_message_t * pSRegister = NULL;

    osip_www_authenticate_t * header = NULL;
    osip_www_authenticate_init(&header);

    osip_www_authenticate_set_auth_type (header,osip_strdup("Digest"));
    osip_www_authenticate_set_realm(header,osip_enquote(pcRealm));
    osip_www_authenticate_set_nonce(header,osip_enquote(pcNonce));

    char *pDest = NULL;
    osip_www_authenticate_to_str(header,&pDest);

    iReturnCode = eXosip_message_build_answer(pSipEvt->tid,SIP_UNAUTHORIZED,&pSRegister);
    if ( iReturnCode == 0 && pSRegister != NULL )
    {
        osip_message_set_www_authenticate(pSRegister,pDest);
        eXosip_message_send_answer(pSipEvt->tid,SIP_UNAUTHORIZED,pSRegister);
    }

    osip_www_authenticate_free(header);
    osip_free(pDest);
}
int SendMessageAnswer(int Tid, int Code, bool bSetTime = false, int Expires = 0, TraverseAddr_T *pTvsAddr = NULL)
{
#define CLIP_BUFFER_SIZE               256
#define USERNAME_MAX_LEN               256
#define DOMAIN_MAX_LEN                 64
#define PASSWD_MAX_LEN                 64
#define SIP_AOR_MAX_SIZE               (USERNAME_MAX_LEN + DOMAIN_MAX_LEN)
    osip_message_t * pMsgAnswer = NULL;
    int ret = eXosip_message_build_answer(Tid, Code,&pMsgAnswer);
    if(ret == OSIP_SUCCESS && pMsgAnswer != NULL )
    {
        if (bSetTime == true)
        {
            char TmpBuf[CLIP_BUFFER_SIZE+1] = {0};
            snprintf(TmpBuf, CLIP_BUFFER_SIZE, "%d", (int)Expires);
            osip_message_set_expires(pMsgAnswer, TmpBuf);

            osip_header_t*  pHeader = NULL;
            int iRet = osip_header_init(&pHeader);
            if (OSIP_SUCCESS == iRet)
            {
                time_t now;
                struct timeval tv;
                gettimeofday(&tv,NULL);
                now = tv.tv_sec;
                struct tm STm = {0};
                struct tm* pTmVal = localtime_r(&now, &STm);  //使用线程安全函数
                if (pTmVal != NULL)
                {
                    //系统启用夏令时,需要减去一个小时。
                    if (1 == pTmVal->tm_isdst)
                    {
                        now -= 3600;
                        pTmVal = localtime_r(&now, &STm);  //使用线程安全函数
                        if (NULL == pTmVal)
                            return -1;
                    }

                    //2010-07-26 16:05:10
                    snprintf(TmpBuf, CLIP_BUFFER_SIZE, "%4d-%02d-%02dT%02d:%02d:%02d.%03d", pTmVal->tm_year+1900, pTmVal->tm_mon+1, pTmVal->tm_mday, pTmVal->tm_hour, \
						pTmVal->tm_min, pTmVal->tm_sec,(int)(tv.tv_usec/1000));

                    //set Date field
                    osip_header_set_name(pHeader, osip_strdup("Date") );
                    osip_header_set_value(pHeader, osip_strdup(TmpBuf) );
                    osip_list_add(&pMsgAnswer->headers, pHeader, -1);
                }
            }

            if (pTvsAddr != NULL)
            {
                osip_header_t*  pHeader2 = NULL;
                iRet = osip_header_init(&pHeader2);
                if (OSIP_SUCCESS == iRet)
                {
                    char Contact[SIP_AOR_MAX_SIZE + 1] = {0};
                    snprintf(Contact, SIP_AOR_MAX_SIZE, "sip:%s@%s:%d", pMsgAnswer->to->url->username, pTvsAddr->OutIpAddr, pTvsAddr->OutPort);
                    osip_header_set_name(pHeader2, osip_strdup("Contact") );
                    osip_header_set_value(pHeader2, osip_strdup(Contact) );
                    osip_list_add(&pMsgAnswer->headers, pHeader2, -1);
                }
            }
        }
        eXosip_message_send_answer(Tid, Code, pMsgAnswer);
    }
    else
    {
        DBGPrint(M_SipUA, ERROR_LEVEL, "%s:Build Message Answer Failed Tid<%d> Code<%d>, ret:%d!", __FUNCTION__, Tid, Code, ret);
    }
    return 0;
}


int ProceXsipEvt(eXosip_event_t* pSipEvt){
    if (NULL == pSipEvt){
        DBGPrint(M_SipUA, ERROR_LEVEL, "%s: pSipEvt is null pointer!", __FUNCTION__);
        return -1;
    }

    printf("type: %d, textinfo: %s, tid: %d, tid:%d, sip_method:  %s\r\n", pSipEvt->type, pSipEvt->textinfo, pSipEvt->tid, pSipEvt->tid, pSipEvt->request->sip_method);

    int iRet = -1;
    osip_authorization_t *pWWWAuEcho = NULL;
    iRet = osip_message_get_authorization(pSipEvt->request, 0, &pWWWAuEcho);
    if(pWWWAuEcho == NULL){
        printf("摄像机第一次发来未鉴权注册信息,返回401  %d\r\n" , iRet);  // 摄像机第一次发来未鉴权注册信息,返回401
        Register401Unauthorized(pSipEvt);
    }else{
        printf(" 摄像机第二次发来鉴权注册信息,返回200 OK %d\r\n" , iRet);
        osip_message_t* pRegisterMsg = NULL;
        pRegisterMsg = pSipEvt->request;
        SendMessageAnswer(pSipEvt->tid, SIP_OK, true, 3600, &pRegisterMsg->TvsAddr);
    }
    return 0;
}

实现注销功能

理论

  1.  注销的Expires必须为0,表示注销。
    
  2.  SIP服务器回401要求携带Authorization信息。
    
  3.  IPC向SIP服务器发送认证信息,认证信息和注册时候的认证信息相同。
    
  4.   注销成功SIP服务器会返回200OK。
    

整个过程如下(总结)

在这里插入图片描述

参考

  • 9
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
GB28181-2016是中国国家标准中关于视频监控设备及系统的技术要求的规定。而IPC是指网络摄像机,即采用网络传输视频信号并能够通过网络进行控制和管理的摄像机。模拟设备是指传统的模拟视频监控设备,如模拟摄像机和模拟录像机等。 在GB28181-2016中,IPC模拟设备注册WVP是指基于WVP(设备安全接入协议)的IPC模拟设备注册过程。IPC模拟设备要实现在GB28181-2016标准下进行网络视频监控,需首先通过WVP注册到视频监控系统中,以便能够被系统识别和管理。 具体而言,在IPC模拟设备注册WVP过程中,设备首先要获得WVP认证,在网络中获取唯一的标识符,如设备ID。然后,设备需要与管理服务器建立通信连接,通过发送注册请求进行注册。管理服务器根据设备ID和其他相关信息,验证设备的合法性,并生成相应的授权信息,如设备密钥等。设备通过接收管理服务器的应答,完成注册过程。 完成注册后,IPC模拟设备可以与视频监控系统进行通信,并通过相关协议和接口与系统中的其他设备进行交互,如视频录制、云存储、远程监控等功能。此外,IPC模拟设备还能够通过视频监控系统进行实时视频传输、编码设置、告警处理等操作,实现高效的网络视频监控。 总而言之,GB28181-2016规定了IPC模拟设备注册WVP的过程,使得这些设备能够在视频监控系统中被识别和管理,并实现网络视频监控的各项功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值