Как позвонить на телефон используя C++ и SIP?....
Что понадобится!?
в коде есть функция MD5Digest, в которой происходит этот необходимый расчет.
https://www.gnu.org/software/osip/
https://www.wireshark.org/download.html
Как работать с 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;
{
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;
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 по адресу, которую необходимо добавить к проету
//code from https://github.com/ulwanski/md5/blob/master
Источники:
https://www.wireshark.org/download.html
Гольдштейн Б.С., Зарубин А.А., Саморезов В.В. Протокол SIP l
Комментариев нет:
Отправить комментарий