среда, 28 февраля 2018 г.

Как позвонить на телефон используя C++ и SIP?....

 Как позвонить на телефон используя C++ и SIP?....


Как работать с sip с использованием C++? Это интересный вопрос задавал в google пока не прочитал rfc по sip  + еще несколько справочников + несколько статей в интернете.

Моя задача была простая, взять sip аккаунт, позвонить на телефон, и все.


Что понадобится!?
- Скомпилированная библиотека osip(GNU), я её использовал лишь для парсинга ответов, это библиотека действительно очень мощная, с ней можно написать софт(для sip), практически любой сложности. Единственный недостаток данной библиотеки является это отсутствие хорошей документации.

- VS 2010 C++

Я Буду использовать WINSOCK, поэтому не буду использовать не каких сторонних библиотек, для работы с сетью.

Из sip протокола необходимо пониманием авторизации, как это происходит?! Обратимся к картинки из той-же wiki.

Это типичная схема авторизации. Схема авторизации чего спросите вы?! – авторизации на sip сервере разумеется, как и на любом другом сервисе.

Помимо этого следует так-же знать об методе аутентификации пользователя с помощью
Дайджест-аунтенификации, обратимся опять-же к вики и посмотрим на картику. Для чего это надо!? Все дело в том что sip сервер использует именно эти методы.



в коде есть функция MD5Digest, в которой происходит этот необходимый расчет.

Необходима была и непосредственно схема сессии (это когда авторизация прошла, вы набираете номер, оборудование делает до звон, пользователь снимает трубку(говорит) далее кладёт трубку, сессия завершается) эту схему я взял из чтения rfc.


Схема INVITE отображает инициализацию сессии без использования Proxy Authentication
Следующая схема из rfc показывает INVITE сессию, но с использованием
Proxy- Authentication.


Эти схемы мне очень помогли справиться с задачей которая у меня была! Если вы не знаете  с чего начать, для изучения sip, начните с изучения методов, в принципе 1-ых двух вам будет достаточно чтобы позвонить на телефон ;)
(REGISTER, INVITE,  BYE, CANCEL).

Пишем код!

MAIN.CPP

#include "network.h"
#include <stdio.h>

#define  HOST "vsemsignal.ru" //sip host
#define  ULOGIN "XXX" //Пользователь
#define  UPASSW "XXXXXXXXXX" //Пароль.

int main()
{

//Инициализация моей библиотеки с сетью!
    if(!LibInit())
return -1;

//Это gnu osip (нужна для парсинга ответов)
   osip_t *osip;
   if(osip_init(&osip) != 0)
return - 1;

//Подготовка socket’a
    PSOCK_INFO hInfo = Init("78.24.219.184", 5060); //IP + Port sip сервера.
    if(hInfo)
   {
   if(AuthClient(hInfo, HOST, ULOGIN, UPASSW)) //Авторизуемся на сервере
   {
//Звоним клиенту!
if(InviteAndCall(hInfo,
"78.24.219.184",
HOST,
ULOGIN,
UPASSW,
"798XXXXXXXX"))
{
//Job Finish Success!
}
   }
    
  Release(hInfo);
}

LibRelease();
   osip_release(osip);

return 0;
}

//Авторизация на хосте, тут отправляется запрос REGISTER, сервер ответчает исходя из схемы регистрации смотри сверху.

bool AuthClient(PSOCK_INFO hInfo,
const char * domen,
const char * user,
const char * password)
{

bool bRet = false;
char cbBuff_1[1024],
cbBuff_2[1024];

CreateRegisterRequest(domen, user, password, 0, cbBuff_2);

if(SendRequest(hInfo, cbBuff_2))
{
memset(cbBuff_1, 0, sizeof(cbBuff_1));
if(RecvResponse(hInfo, cbBuff_1, sizeof(cbBuff_1)))
{
PRESPONSE_INFO lpResponseInfp = GetResponseInfo(cbBuff_1);
if(lpResponseInfp)
{
if(lpResponseInfp->sip->status_code == 401) //
{
Md5Digest(cbBuff_1,
user,
lpResponseInfp->dest->realm,
password,
"sip:vsemsignal.ru",
"REGISTER",
lpResponseInfp->dest->nonce);

//Prepeare Request for Register...with auth info...
CreateRegisterRequest(domen, user, password, cbBuff_1, cbBuff_2);

if(SendRequest(hInfo, cbBuff_2))
{
memset(cbBuff_1, 0, sizeof(cbBuff_1));
if(RecvResponse(hInfo, cbBuff_1, sizeof(cbBuff_1) - 1))
{
PRESPONSE_INFO lpAuthInfoResponse = GetResponseInfo(cbBuff_1);
if(lpAuthInfoResponse)
{
if(lpAuthInfoResponse->sip->status_code == 200)
bRet = true;
ReleaseResponseInfo(lpAuthInfoResponse);
}
}
}
}
   ReleaseResponseInfo(lpResponseInfp);
}
}
}

return bRet;
}

// А тут мы звоним до пользователя!

bool InviteAndCall(PSOCK_INFO hInfo,
  const char * ip,
  const char * domen,
  const char * user,
  const char * password,
  const char * telephone)
{

bool bRet = false;
char cbBuff_1[2048], cbBuff_2[2048];
CreateInviteRequest(domen, user, telephone, cbBuff_1);
if(SendRequest(hInfo, cbBuff_1))
{
memset(cbBuff_1, 0, sizeof(cbBuff_1));
while(RecvResponse(hInfo, cbBuff_1, sizeof(cbBuff_1)))
{
PRESPONSE_INFO lpResponseInfp = GetResponseInfo(cbBuff_1);
if(lpResponseInfp)
{
//Сервер требует работы через прокси!?
if(lpResponseInfp->sip->reason_phrase)
{
if(std::string(lpResponseInfp->sip->reason_phrase)  
== std::string("Proxy Authentication Required")
&& lpResponseInfp->proxy)
{
Md5Digest(cbBuff_2,
user,
lpResponseInfp->proxy->realm,
password,
"sip:vsemsignal.ru",
"INVITE",
lpResponseInfp->proxy->nonce);

CreateInviteRequest(domen, user, telephone, cbBuff_1, cbBuff_2);
bRet = SendRequest(hInfo, cbBuff_1);
break;
}
}
ReleaseResponseInfo(lpResponseInfp);
}
}
}
 

return bRet;
}

Дополнительные файлы которые потребуются
NETWORK.H
//https://tools.ietf.org/html/rfc3665
#ifndef NETWORK_H__
#define NETWORK_H__
#include "md5.h"
#include <WinSock2.h>
#include <Windows.h>
#include <osip2/osip.h>
#include <osipparser2/osip_parser.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "osip2.lib")
#pragma comment(lib, "osipparser2.lib")
 
bool LibInit();
void LibRelease();
const char * GetLocalIp();

void CreateRegisterRequest(const char * cszHost,
  const char * userLogin,
  const char * userPassword,
  const char * authInfo,
  char * outBuffer);

void CreateInviteRequest(const char * cszHost,
const char * userLogin,
const char * cszTelephoneNumber,
char * outBuffer,
char * authInfo = 0);


int GetCSeq();

void Md5Digest(char *out,
  const char *user,
  const char *realm,
  const char *pass,
  const char *uri,
  const char *method,
  const char *nonce);



typedef struct _SOCK_INFO
{
SOCKET sock;
HOSTENT *hostent;
sockaddr_in dest_addr;

}SOCK_INFO, *PSOCK_INFO;

typedef struct _RESPONSE_INFO
{
osip_message_t  *sip;
osip_www_authenticate_t   *dest;
osip_proxy_authenticate_t *proxy;
}RESPONSE_INFO, *PRESPONSE_INFO;


PSOCK_INFO Init(const char * cszHostIp, unsigned short port);
bool   SendRequest(PSOCK_INFO, const char *);
bool   RecvResponse(PSOCK_INFO, char *, int size);
void   Release(PSOCK_INFO);
PRESPONSE_INFO      GetResponseInfo(const char *);
void        ReleaseResponseInfo(PRESPONSE_INFO);

void NotifyAccept(char * outBuffer,
 const char * cszRemonteIp,
     const char * userLogin,
 const char * cszFromTag,
 const char * cszCallId,
 const char * cszCSeqNumber,
 const char * cszCSeqMethod);

bool AuthClient(const char * cszHostIp,
unsigned short port,
const char * user,
const char * password);


#endif



NETWORK.CPP

#include "network.h"
#include <stdio.h>
#include <regex>


bool LibInit()
{
static WSADATA wsData;
if(!WSAStartup(MAKEWORD(2,2), &wsData))
return true;
return false;
}

void LibRelease()
{
WSACleanup();
}
const char * GetLocalIp()
{
static char cbIp[255];
memset(cbIp, 0, sizeof(cbIp));
if(gethostname(cbIp, sizeof(cbIp)) != SOCKET_ERROR)
{
struct hostent *host = gethostbyname(cbIp);
if(host != NULL)
{
  memset(cbIp, 0, sizeof(cbIp));
  struct in_addr addr;
  addr.s_addr = *(u_long *) host->h_addr_list[0];
  strcpy_s(cbIp, inet_ntoa(addr));
}
}
return cbIp;
}
void CreateRegisterRequest(const char * cszHost,
  const char * userLogin,
  const char * userPassword,
  const char * authInfo,
  char * outBuffer)
{

if(!authInfo)
sprintf(outBuffer,
"REGISTER sip:%s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:5060;branch=z9hG4bK%u;rport\r\n"
   "From: \"TestLib\" <sip:%s@%s>;tag=7h%u\r\n"
"To: \"TestLib\" <sip:%s@%s>\r\n"
"Call-ID: 80E7C3BA-101A-E811-8006-1CF2C761A6D8@%s\r\n"
"CSeq: %d REGISTER\r\n"
"Contact: <sip:%s@%s:5060>\r\n"
"Allow: INVITE, ACK, BYE, CANCEL, INFO, MESSAGE, NOTIFY, OPTIONS, REFER, UPDATE, PRACK\r\n"
"Max-Forwards: 70\r\n"
"User-Agent: Softphone Beta1.5\r\n"
"Expires: 900\r\n"
"Content-Length: 0\r\n"
"\r\n",
cszHost,
GetLocalIp(),
osip_build_random_number(),
userLogin,
cszHost,
osip_build_random_number(),
userLogin,
cszHost,
GetLocalIp(),
GetCSeq(),
userLogin,
GetLocalIp());
else
sprintf(outBuffer,
"REGISTER sip:%s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:5060;branch=z9hG4bK%u;rport\r\n"
"From: \"TestLib\" <sip:%s@%s>;tag=7h%u\r\n"
"To: \"TestLib\" <sip:%s@%s>\r\n"
"Call-ID: 80E7C3BA-101A-E811-8006-1CF2C761A6D8@%s\r\n"
"CSeq: %d REGISTER\r\n"
"Contact: <sip:%s@%s:5060>\r\n"
"Authorization: %s\r\n"
"Allow: INVITE, ACK, BYE, CANCEL, INFO, MESSAGE, NOTIFY, OPTIONS, REFER, UPDATE, PRACK\r\n"
"Max-Forwards: 70\r\n"
"User-Agent: Softphone Beta1.5\r\n"
"Content-Length: 0\r\n"
"\r\n",
cszHost,
GetLocalIp(),
   osip_build_random_number(),
userLogin,
cszHost,
osip_build_random_number(),
userLogin,
cszHost,
GetLocalIp(),
GetCSeq(),
userLogin,
GetLocalIp(),
authInfo);
}

void CreateInviteRequest(const char * cszHost,
const char * userLogin,
const char * cszTelephoneNumber,
char * outBuffer,
char * authInfo)
{

char cszIniteBody[1024];  
memset(cszIniteBody, 0, sizeof(cszIniteBody));
sprintf(cszIniteBody,      
"v=0\r\n"
"o=- 2611323715 1 IN IP4 %s\r\n"
"s=SIPPER for TestLib\r\n"
"c=IN IP4 %s\r\n"
"t=0 0\r\n"
"m=audio 5062 RTP/AVP 107 8 0 2 3 97 110 111 9 18 11 118 101\r\n"
"a=rtpmap:107 opus/48000/2\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=rtpmap:0 PCMU/8000\r\n"
"a=rtpmap:2 G726-32/8000\r\n"
"a=rtpmap:3 GSM/8000\r\n"
"a=rtpmap:97 iLBC/8000\r\n"
"a=rtpmap:110 speex/8000\r\n"
"a=rtpmap:111 speex/16000\r\n"
"a=rtpmap:9 G722/8000\r\n"
"a=rtpmap:18 G729/8000\r\n"
"a=fmtp:18 annexb=yes\r\n"
"a=rtpmap:11 L16/44100\r\n"
"a=rtpmap:118 L16/16000\r\n"
"a=rtpmap:101 telephone-event/8000\r\n"
"a=fmtp:101 0-16\r\n"
"a=ssrc:2958426514\r\n"
"a=sendrecv",
GetLocalIp(),
GetLocalIp());
if(!authInfo)
sprintf(outBuffer,
"INVITE sip:%s@%s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:5060;branch=z9hG4bK%u;rport\r\n"
"From: \"TestLib\" <sip:%s@%s>;tag=7h%u\r\n"
"To: <sip:%s@%s>\r\n"
"Call-ID: 80E7C3BA-101A-E811-8006-1CF2C761A6D8@%s\r\n"
"CSeq: %d INVITE\r\n"
"Contact: <sip:%s@%s:5060>\r\n"
"Content-Type: application/sdp\r\n"
"Mime-Version: 1.0\r\n"
"Allow: INVITE, ACK, BYE, CANCEL, INFO, MESSAGE, NOTIFY, OPTIONS, REFER, UPDATE, PRACK\r\n"
"Max-Forwards: 70\r\n"
"P-Early-Media: supported\r\n"
"User-Agent: SIPPER for PhonerLite\r\n"
"Session-Expires: 1800\r\n"
"P-Preferred-Identity: <sip:%s@%s>\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
cszTelephoneNumber,
cszHost,
GetLocalIp(),
   osip_build_random_number(),
userLogin,
cszHost,
osip_build_random_number(),
cszTelephoneNumber,
cszHost,
GetLocalIp(),
GetCSeq(),
userLogin,
GetLocalIp(),
userLogin,
cszHost,
strlen(cszIniteBody),
cszIniteBody
);
else
{
sprintf(outBuffer,
"INVITE sip:%s@%s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:5060;branch=z9hG4bK%u;rport\r\n"
"From: \"TestLib\" <sip:%s@%s>;tag=7h%u\r\n"
"To: <sip:%s@%s>\r\n"
"Call-ID: 80E7C3BA-101A-E811-8006-1CF2C761A6D8@%s\r\n"
"CSeq: %d INVITE\r\n"
"Contact: <sip:%s@%s:5060>\r\n"
"Content-Type: application/sdp\r\n"
"Mime-Version: 1.0\r\n"
"Allow: INVITE, ACK, BYE, CANCEL, INFO, MESSAGE, NOTIFY, OPTIONS, REFER, UPDATE, PRACK\r\n"
"Max-Forwards: 70\r\n"
"P-Early-Media: supported\r\n"
"User-Agent: SIPPER for PhonerLite\r\n"
"Session-Expires: 1800\r\n"
"Proxy-Authorization: %s\r\n"
"P-Preferred-Identity: <sip:%s@%s>\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
cszTelephoneNumber,
cszHost,
GetLocalIp(),
osip_build_random_number(),
userLogin,
cszHost,
osip_build_random_number(),
cszTelephoneNumber,
cszHost,
GetLocalIp(),
GetCSeq(),
userLogin,
GetLocalIp(),
authInfo,
userLogin,
cszHost,
strlen(cszIniteBody),
cszIniteBody
);
}
}

int GetCSeq()
{
static int cseq = 1;
return cseq++;
}

void Md5Digest(char *out,
const char *user,
const char *realm,
const char *pass,
const char *uri,
const char *method,
const char * nonce)
{
const char *cnonce  = "802a8fe84319e8118005727467f0c10f";
const char *qop = "auth";

 
std::string ha1, ha2, response;
ha2  = method;
ha2  += ":";
ha2  += uri;
ha2 = md5(ha2);
std::string realamNew = realm;
realamNew.erase(std::remove(realamNew.begin(), realamNew.end(), '"'),
realamNew.end());

std::string nonceNew = nonce;
nonceNew.erase(std::remove(nonceNew.begin(), nonceNew.end(), '"'),
nonceNew.end());
 
ha1 = user;
ha1 += ":";
ha1 += realamNew;
ha1 += ":";
ha1 += pass;
 
ha1 = md5(ha1);

response = ha1;
response += ":";
response += nonceNew.c_str();
response += ":";
response += "00000001";
response += ":";
response += cnonce;
response += ":";
response += qop;
response += ":";
response += ha2;
 

response = md5(response);


sprintf(out,
"Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", algorithm=MD5, cnonce=\"%s\", qop=%s, nc=%08x",
user, realamNew.c_str(), nonceNew.c_str(), uri, response.c_str(), cnonce, qop, 1);
}


PSOCK_INFO Init(const char * cszHostIp, unsigned short port)
{
PSOCK_INFO ptrInfo = new SOCK_INFO;
ptrInfo->sock = socket(AF_INET, SOCK_DGRAM, 0);
if(ptrInfo->sock == SOCKET_ERROR)
{
delete ptrInfo;
return 0;
}

ptrInfo->dest_addr.sin_family = AF_INET;
ptrInfo->dest_addr.sin_port = htons(port);

if (inet_addr(cszHostIp))
ptrInfo->dest_addr.sin_addr.s_addr=inet_addr(cszHostIp);
else
if (ptrInfo->hostent=gethostbyname(cszHostIp))
ptrInfo->dest_addr.sin_addr.s_addr=((unsigned long **)
ptrInfo->hostent->h_addr_list)[0][0];
else
{
printf("Unknown host: %d\r\n",WSAGetLastError());
closesocket(ptrInfo->sock);
delete ptrInfo;
return 0;
}
   return ptrInfo;
}

bool  SendRequest(PSOCK_INFO ptrInfo, const char * data)
{
if(ptrInfo->sock == SOCKET_ERROR)
return false;

int iBytesSend = sendto(ptrInfo->sock,
data,
strlen(data),
0,
(sockaddr *)&ptrInfo->dest_addr,
sizeof(ptrInfo->dest_addr));
return true;
}

bool  RecvResponse(PSOCK_INFO ptrInfo, char * buffer, int size)
{
if(ptrInfo->sock == SOCKET_ERROR)
return false;

sockaddr_in server_addr;
int server_addr_size = sizeof(server_addr);
int iBytesRecv = recvfrom(ptrInfo->sock,
 buffer, size,
 0,
 (sockaddr *)&server_addr,
 &server_addr_size);
return true;
}

void  Release(PSOCK_INFO ptrInfo)
{
if(ptrInfo->sock != SOCKET_ERROR)
closesocket(ptrInfo->sock);
delete ptrInfo;
}
PRESPONSE_INFO GetResponseInfo(const char * buffer)
{
PRESPONSE_INFO lpInfo = new RESPONSE_INFO();
memset(lpInfo, 0, sizeof(RESPONSE_INFO));
int i;
i = osip_message_init(&lpInfo->sip);
if (i == 0)
{
i = osip_message_parse(lpInfo->sip, buffer, strlen (buffer));
if(i == 0)
{
osip_message_get_www_authenticate(lpInfo->sip, 0, &lpInfo->dest);
osip_message_get_proxy_authenticate(lpInfo->sip, 0, &lpInfo->proxy);

if(lpInfo->sip)
return lpInfo;
}
osip_message_free(lpInfo->sip);
}
return 0;
}

void  ReleaseResponseInfo(PRESPONSE_INFO lpInfo)
{
if(lpInfo->sip)
osip_message_free(lpInfo->sip);
delete lpInfo;
}

void NotifyAccept(char * outBuffer,
 const char * cszRemonteIp,
 const char * userLogin,
 const char * cszFromTag,
 const char * cszCallId,
 const char * cszCSeqNumber,
 const char * cszCSeqMethod)
{
sprintf(outBuffer,  
"SIP/2.0 200 OK\r\n"
"Via: SIP/2.0/UDP %s:5060;rport=5060;branch=z9hG4bK%u\r\n"
"From: <sip:%s@%s:5060>;tag=%s\r\n"
"To: <sip:%s@%s:5060>;tag=7h%u\r\n"
"Call-ID: %s\r\n"
"CSeq: %s %s\r\n"
"Contact: <sip:%s@%s:5060>\r\n"
"Allow: INVITE, ACK, BYE, CANCEL, INFO, MESSAGE, NOTIFY, OPTIONS, REFER, UPDATE, PRACK\r\n"
"Server: SIPPER for PhonerLite\r\n"
"Content-Length: 0\r\n\r\n",
cszRemonteIp,
osip_build_random_number(),
userLogin,
cszRemonteIp,
cszFromTag,
userLogin,
cszRemonteIp,
osip_build_random_number(),
cszCallId,
cszCSeqNumber,
cszCSeqMethod,
userLogin,
GetLocalIp());
}

Для расчета md5 хешей я использовал библиотеку с git’ub по адресу, которую необходимо добавить к проету


Источники:

https://www.gnu.org/software/osip/
https://www.wireshark.org/download.html

Гольдштейн Б.С., Зарубин А.А., Саморезов В.В. Протокол SIP l

Комментариев нет:

Отправить комментарий