目录
参考目录
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 细节梳理
- 使用REGISTER方法进行注册,注销。
- 支持两种认证方式:数字摘要,数字证书。
- 注册过期时间可以配置,不应小于3600s。
- 注册过期前,需要SIP UA重新发起一次注册。
- 注册失败,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,
®ister_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.1) auth->message_qop == NULL
-
- 3.2) auth->message_qop == “auth”
a) 获取HA2结果,并转换为"hex"形式。计算格式:exp = REGISTER:<sipServerUrI>
b) 响应计算格式:
- 3.2) auth->message_qop == “auth”
如果 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>:
- 3.3) auth->message_qop == “auth-int”
调用:
宏定义:
#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);
}