GB28181开发-2-客户端-注册和注销

目录

1.GB28181开发-1-基础内容标记

参考目录

https://gitee.com/Vanishi/BXC_SipServer.git
https://github.com/lyyyuna/gb28181_client.git

一、注册消息示范(附件J)

1.1 注册信令

在这里插入图片描述


二、注册,注销基本要求

  • IP客户端、网关、SIP设备、联网系统等SIP代理(SIPUA)使用IETFRFC3261中定义的方法Register进行注册和注销。
  • 注册和注销时应进行认证, 认证方式应支持数字摘要认证方式;
  • 高安全级别的宜支持数字证书认证方式; 数字证书的格式符合附录I中的规定。
  • SIP代理在注册过期时间到来之前,应向注册服务器进行刷新注册。刷新注册消息流程应与9.1.2.1的流程描述一致,并遵循IETFRFC3261对刷新注册的规定。
  • 若注册失败,SIP代理应间隔一定时间后继续发起注册过程,与上一次注册时间间隔应可调,一般情况下不应短于60s。
  • 系统、设备注册过期时间应可配置,缺省值为86400s(1d),应在注册过期时间到来之前发送刷新注册消息,为SIP服务器预留适当刷新注册处理时间,注册过期时间不应短于3600s。
  • SIP代理注册成功则认为SIP服务器为在线状态,注册失败则认为SIP服务器为离线状态;SIP服务器在SIP代理注册成功后认为其为在线状态,SIP代理注册过期则认为其为离线状态。

2.1 细节梳理

  1. 使用REGISTER方法进行注册,注销。
  2. 支持两种认证方式:数字摘要,数字证书。
  3. 注册过期时间可以配置,不应小于3600s。
  4. 注册过期前,需要SIP UA重新发起一次注册。
  5. 注册失败,SIP UA需要间隔重新发起注册。重新发起注册间隔可调,不小于60s。

2.2 基本注册

在这里插入图片描述


2.2.1 SIP Device类


class Device {
public:
    Device() {}

    Device(string server_sip_id, string server_ip, int server_port,
            string device_sip_id, string username, string password,
            int local_port,
            string manufacture,
            string filepath): 
            server_sip_id(server_sip_id), 
            server_ip(server_ip),
            server_port(server_port),
            device_sip_id(device_sip_id),
            username(username),
            password(password),
            local_port(local_port),
            manufacture(manufacture),
            filepath(filepath) {
        sip_context = nullptr;
        is_running = false;
        is_register = false;
        local_ip = string(128, '0');
#if 0
        load(filepath.c_str());
#endif
    }

    ~Device(){}

    void start();

    void stop();

    void process_request();

    void process_catalog_query(string sn);

    void process_deviceinfo_query(string sn);

    void process_devicestatus_query(string sn);

    void process_devicecontrol_query(string sn);

    void heartbeat_task();

    void send_request(osip_message_t * request);

    void send_response(shared_ptr<eXosip_event_t> evt, osip_message_t * msg);

    osip_message_t * create_msg();

    void send_response_ok(shared_ptr<eXosip_event_t> evt);

    std::tuple<string, string> get_cmd(const char * body);

    void push_rtp_stream();

public:
    string server_sip_id;
    string server_ip;
    int server_port;
    string device_sip_id;
    string username;
    string password;
    string local_ip;
    int local_port;

    string manufacture;
    string rtp_ip;
    int rtp_port;
    string rtp_protocol;

    string filepath;

private:
    eXosip_t* sip_context;
    bool is_running;
    bool is_register;
    bool is_pushing;

    string from_sip;
    string to_sip;
    string ssrc;

    int sockfd;
    int bind();
    void send_network_packet(const char * data, int length);
};


2.2.2 SIP监听

void Device::start() {
...

	sip_context = eXosip_malloc();
	
/**
 * Initiate the eXtented oSIP library.
 * 
 * @param excontext    eXosip_t instance.
 */
    if (OSIP_SUCCESS != eXosip_init(sip_context)) {
        spdlog::error("sip init failed.");
        return;
    }
/**
 * Listen on a specified socket.
 * 
 * @param excontext    eXosip_t instance.
 * @param transport IPPROTO_UDP for udp. (soon to come: TCP/TLS?)
 * @param addr      the address to bind (NULL for all interface)
 * @param port      the listening port. (0 for random port)
 * @param family    the IP family (AF_INET or AF_INET6).
 * @param secure    0 for UDP or TCP, 1 for TLS (with TCP).
 */
    if (OSIP_SUCCESS != eXosip_listen_addr(sip_context, IPPROTO_UDP, nullptr, local_port, AF_INET, 0)) {
        spdlog::critical("sip port bind failed.");
        eXosip_quit(sip_context);
        sip_context = nullptr;
        return;
    }
...
}

2.2.3 SIP代理发送Regitster请求:

void Device::start() {
...
    is_running = true;

    ostringstream from_uri;
    ostringstream contact;
    ostringstream proxy_uri;

    // local ip & port
    //获取一个本地可用网卡的ip地址
    /**
 * Find the current localip (interface with default route).
 * 
 * @param excontext    eXosip_t instance.
 * @param family    AF_INET or AF_INET6
 * @param address   a string containing the local IP address.
 * @param size      The size of the string
 */
    eXosip_guess_localip(sip_context, AF_INET, (char *)local_ip.data(), local_ip.length());
    spdlog::info("local ip is {}", local_ip);

    from_uri << "sip:" << device_sip_id << "@" << local_ip << ":" << local_port;
    contact << "sip:" << device_sip_id << "@" << local_ip << ":" << local_port;
    proxy_uri << "sip:" << server_sip_id << "@" << server_ip << ":" << server_port;

    from_sip = from_uri.str();
    to_sip = proxy_uri.str();

    spdlog::info("from uri is {}", from_sip);
    spdlog::info("contact is {}", contact.str());
    spdlog::info("proxy_uri is {}", to_sip);

    // clear auth
    eXosip_clear_authentication_info(sip_context);

/**
 * Build initial REGISTER request.
 * 
 * @param excontext    eXosip_t instance.
 * @param from      SIP url for caller.
 * @param proxy     Proxy used for registration.
 * @param contact   Contact address. (optional)
 * @param expires   The expires value for registration.
 * @param reg       The SIP request to build.
 */
    osip_message_t * register_message = nullptr;
    int register_id = eXosip_register_build_initial_register(
										sip_context, 
										from_sip.c_str(), 
										to_sip.c_str(), 
										contact.str().c_str(), 
										3600, 
										&register_message);
                    
    if (nullptr == register_message) {
        spdlog::error("eXosip_register_build_initial_register failed");
        return;
    }

	eXosip_lock(sip_context);
	eXosip_register_send_register(sip_context, register_id, register_message);
	eXosip_unlock(sip_context);

	thread heartbeat_task_thread(&Device::heartbeat_task, this);
	heartbeat_task_thread.detach();
	
	this->process_request();
...
}

Register报文:
在这里插入图片描述


梳理

  • eXosip_register_build_initial_register()函数构建注册信令,并返回注册上下文的唯一标识符 。
  • Register报文中,Message Header中Contact,From,To都指向SIP代理。
  • proxy指向SIP服务器。

2.2.4 UAS 返回401响应,www_authenticate携带认证信息。


2.2.5 UAC请求,并携带www-auth认证字段

在此处主要是采用MD5进行计算。

  • 1.MD5与MD5-sess计算格式:

1)MD5计算格式:exp1 = sername:realm:passwd
2)MD5-SESS计算格式,首先获取exp1的加密结果exp1_resualt: exp2 = exp1_resualt:nonce:nonce_count

  • 2.将对应加密结果,转成“hex”形式,存储在32个字节数组中;

注意: 接收md5和"hex"形式的两个空间,要比实际长度大1,用于存储’\0’

  • 3.根据HTTP摘要规范计算请求摘要/响应摘要

sipServerUrI格式,指sip服务端信息:sip:id@ip:5060

    • 3.1) auth->message_qop == NULL
      a) 获取HA2结果,并转换为"hex"形式。计算格式:exp = REGISTER:<sipServerUrI>
      b) 响应计算格式:exp = has1_hex:nonce:has2_hex
    • 3.2) auth->message_qop == “auth”
      a) 获取HA2结果,并转换为"hex"形式。计算格式:exp = REGISTER:<sipServerUrI>
      b) 响应计算格式:
如果 Aka !=  0
		exp = has1_hex:nonce:has2_hex;
否则, 
		exp =has1_hex:nonce:nonce_count:auth->cnonce:has2_hex;
    • 3.3) auth->message_qop == “auth-int”
      与3.2流程一样,只需要在步骤a基础上,稍微修改:exp = REGISTER:<sipServerUrI>:

调用:

宏定义:
#define HASHLEN 16
typedef char HASH[HASHLEN];
#define HASHHEXLEN 32
typedef char HASHHEX[HASHHEXLEN+1];
#define IN
#define OUT
...

void SipServer::response_register(eXosip_event_t *evtp) {
osip_authorization_t * auth = nullptr;
    osip_message_get_authorization(evtp->request, 0, &auth);

    if(auth && auth->username){

        char *method = NULL, // REGISTER
        *algorithm = NULL, // MD5
        *username = NULL,// 340200000013200000024
        *realm = NULL, // sip服务器传给客户端,客户端携带并提交上来的sip服务域
        *nonce = NULL, //sip服务器传给客户端,客户端携带并提交上来的nonce
        *nonce_count = NULL,
        *uri = NULL; // sip:34020000002000000001@3402000000

        osip_contact_t *contact = nullptr;
        osip_message_get_contact (evtp->request, 0, &contact);

        method = evtp->request->sip_method;
        char calc_response[HASHHEXLEN];
        HASHHEX HA1, HA2 = "", Response;

#define SIP_STRDUP(field) if (auth->field) (field) = osip_strdup_without_quote(auth->field)

        SIP_STRDUP(algorithm);
        SIP_STRDUP(username);
        SIP_STRDUP(realm);
        SIP_STRDUP(nonce);
        SIP_STRDUP(nonce_count);
        SIP_STRDUP(uri);


        DigestCalcHA1(algorithm, username, realm, mInfo->getSipPass(), nonce, nonce_count, HA1);
        DigestCalcResponse(HA1, nonce, nonce_count, auth->cnonce, auth->message_qop, 0, method, uri, HA2, Response);

        HASHHEX temp_HA1;
        HASHHEX temp_response;
        DigestCalcHA1("REGISTER", username, mInfo->getSipRealm(), mInfo->getSipPass(), mInfo->getNonce(), NULL, temp_HA1);
        DigestCalcResponse(temp_HA1, mInfo->getNonce(), NULL, NULL, NULL, 0, method, uri, NULL, temp_response);
        memcpy(calc_response, temp_response, HASHHEXLEN);

        Client *client = new Client(strdup(contact->url->host),
                                    atoi(contact->url->port),
                                    strdup(username));

        if (!memcmp(calc_response, Response, HASHHEXLEN)) {
            this->response_message_answer(evtp,200);
            LOGI("Camera registration succee,ip=%s,port=%d,device=%s",client->getIp(),client->getPort(),client->getDevice());

            mClientMap.insert(std::make_pair(client->getDevice(),client));

            this->request_invite(client->getDevice(),client->getIp(),client->getPort());

        } else {
            this->response_message_answer(evtp,401);
            LOGI("Camera registration error, p=%s,port=%d,device=%s",client->getIp(),client->getPort(),client->getDevice());

            delete client;
        }

        osip_free(algorithm);
        osip_free(username);
        osip_free(realm);
        osip_free(nonce);
        osip_free(nonce_count);
        osip_free(uri);
    } else {
        response_register_401unauthorized(evtp);
    }
}

函数定义

void my_CvtHex(
        IN HASH Bin,
        OUT HASHHEX Hex
        )
{
    unsigned short i;
    unsigned char j;
    for (i = 0; i < HASHLEN; i++) {
        j = (Bin[i] >> 4) & 0xf;
        if (j <= 9)
            Hex[i*2] = (j + '0');
        else
            Hex[i*2] = (j + 'a' - 10);
        j = Bin[i] & 0xf;
        if (j <= 9)
            Hex[i*2+1] = (j + '0');
        else
            Hex[i*2+1] = (j + 'a' - 10);
    };
    Hex[HASHHEXLEN] = '\0';
}


void DigestCalcHA1 (IN const char *pszAlg,
               IN const char *pszUserName,
               IN const char *pszRealm,
               IN const char *pszPassword,
               IN const char *pszNonce,
               IN const char *pszCNonce,
               OUT HASHHEX SessionKey)
{
    MD5_CTX Md5Ctx;
    HASH HA1;

    MD5Init (&Md5Ctx);
    MD5Update (&Md5Ctx, (unsigned char *) pszUserName, (unsigned int) strlen (pszUserName));
    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    MD5Update (&Md5Ctx, (unsigned char *) pszRealm, (unsigned int) strlen (pszRealm));
    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    MD5Update (&Md5Ctx, (unsigned char *) pszPassword, (unsigned int) strlen (pszPassword));
    MD5Final ((unsigned char *) HA1, &Md5Ctx);
    if ((pszAlg != NULL) && strcasecmp (pszAlg, "md5-sess") == 0) {
        MD5Init (&Md5Ctx);
        MD5Update (&Md5Ctx, (unsigned char *) HA1, HASHLEN);
        MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
        MD5Update (&Md5Ctx, (unsigned char *) pszNonce, (unsigned int) strlen (pszNonce));
        MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
        MD5Update (&Md5Ctx, (unsigned char *) pszCNonce, (unsigned int) strlen (pszCNonce));
        MD5Final ((unsigned char *) HA1, &Md5Ctx);
    }
    my_CvtHex (HA1, SessionKey);
}


void DigestCalcResponse (IN HASHHEX HA1,     /* H(A1) */
                    IN const char *pszNonce,    /* nonce from server */
                    IN const char *pszNonceCount,       /* 8 hex digits */
                    IN const char *pszCNonce,   /* client nonce */
                    IN const char *pszQop,      /* qop-value: "", "auth", "auth-int" */
                    IN int Aka, /* Calculating AKAv1-MD5 response */
                    IN const char *pszMethod,   /* method from the request */
                    IN const char *pszDigestUri,        /* requested URL */
                    IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
                    OUT HASHHEX Response
                    /* request-digest or response-digest */ )
{
    MD5_CTX Md5Ctx;
    HASH HA2;
    HASH RespHash;
    HASHHEX HA2Hex;

    /* calculate H(A2) */
    MD5Init (&Md5Ctx);
    MD5Update (&Md5Ctx, (unsigned char *) pszMethod, (unsigned int) strlen (pszMethod));
    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    MD5Update (&Md5Ctx, (unsigned char *) pszDigestUri, (unsigned int) strlen (pszDigestUri));

    if (pszQop == NULL) {
        goto auth_withoutqop;
    }
    else if (0 == strcasecmp (pszQop, "auth-int")) {
        goto auth_withauth_int;
    }
    else if (0 == strcasecmp (pszQop, "auth")) {
        goto auth_withauth;
    }

auth_withoutqop:
    MD5Final ((unsigned char *) HA2, &Md5Ctx);
    my_CvtHex (HA2, HA2Hex);

    /* calculate response */
    MD5Init (&Md5Ctx);
    MD5Update (&Md5Ctx, (unsigned char *) HA1, HASHHEXLEN);
    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    MD5Update (&Md5Ctx, (unsigned char *) pszNonce, (unsigned int) strlen (pszNonce));
    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);

    goto end;

auth_withauth_int:

    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    MD5Update (&Md5Ctx, (unsigned char *) HEntity, HASHHEXLEN);

auth_withauth:
    MD5Final ((unsigned char *) HA2, &Md5Ctx);
    my_CvtHex (HA2, HA2Hex);

    /* calculate response */
    MD5Init (&Md5Ctx);
    MD5Update (&Md5Ctx, (unsigned char *) HA1, HASHHEXLEN);
    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    MD5Update (&Md5Ctx, (unsigned char *) pszNonce, (unsigned int) strlen (pszNonce));
    MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    if (Aka == 0) {
        MD5Update (&Md5Ctx, (unsigned char *) pszNonceCount, (unsigned int) strlen (pszNonceCount));
        MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
        MD5Update (&Md5Ctx, (unsigned char *) pszCNonce, (unsigned int) strlen (pszCNonce));
        MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
        MD5Update (&Md5Ctx, (unsigned char *) pszQop, (unsigned int) strlen (pszQop));
        MD5Update (&Md5Ctx, (unsigned char *) ":", 1);
    }
end:
    MD5Update (&Md5Ctx, (unsigned char *) HA2Hex, HASHHEXLEN);
    MD5Final ((unsigned char *) RespHash, &Md5Ctx);
    my_CvtHex (RespHash, Response);
}

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@晓乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值