1906 lines
55 KiB
C
1906 lines
55 KiB
C
/* SIP handling
|
|
*
|
|
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include "../libdebug/debug.h"
|
|
#include <sofia-sip/sip_status.h>
|
|
#include <sofia-sip/su_log.h>
|
|
#include <sofia-sip/sdp.h>
|
|
#include <sofia-sip/sip_header.h>
|
|
#include <sofia-sip/stun_tag.h>
|
|
#include <sofia-sip/su_md5.h>
|
|
#include "sip.h"
|
|
|
|
#ifndef SOFIA_SIP_GCC_4_8_PATCH_APLLIED
|
|
#warning ********************************************************
|
|
#warning Please apply the sofia-sip-gcc-4.8.patch to Sofia lib !
|
|
#warning Or compile Sofia lib with "./configure CFLAGS=-O0" !
|
|
#warning ********************************************************
|
|
#endif
|
|
|
|
#undef NUTAG_AUTO100
|
|
|
|
static void invite_option_timeout(struct timer *timer);
|
|
|
|
call_t *call_create(sip_endpoint_t *sip_ep)
|
|
{
|
|
call_t *call, **call_p;
|
|
|
|
call = calloc(1, sizeof(*call));
|
|
if (!call) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "No memory!\n");
|
|
abort();
|
|
}
|
|
|
|
call_p = &sip_ep->call_list;
|
|
while (*call_p)
|
|
call_p = &((*call_p)->next);
|
|
*call_p = call;
|
|
|
|
call->sip_ep = sip_ep;
|
|
|
|
timer_init(&call->invite_option_timer, invite_option_timeout, call);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Created new call\n");
|
|
|
|
return call;
|
|
}
|
|
|
|
void call_destroy(call_t *call)
|
|
{
|
|
call_t **call_p;
|
|
|
|
/* detach */
|
|
call_p = &call->sip_ep->call_list;
|
|
while (*call_p) {
|
|
if (*call_p == call)
|
|
break;
|
|
call_p = &((*call_p)->next);
|
|
}
|
|
*call_p = call->next;
|
|
|
|
free(call->sdp_request);
|
|
free(call->sdp_response);
|
|
timer_exit(&call->invite_option_timer);
|
|
|
|
free(call);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroyed call instance\n");
|
|
}
|
|
|
|
static const char *state_names[] = {
|
|
"IDLE",
|
|
"OUT-INVITE",
|
|
"IN-INVITE",
|
|
"CONNECT",
|
|
"OUT-RELEASE",
|
|
};
|
|
|
|
static void new_state(call_t *call, enum sip_state state)
|
|
{
|
|
if (call->state == state)
|
|
return;
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Changing state %s -> %s\n", state_names[call->state], state_names[state]);
|
|
call->state = state;
|
|
}
|
|
|
|
/* replace all internet contact lines ("c=IN ") by given type and address */
|
|
static const char *sdp_replace_contact(const char *input, const char *address)
|
|
{
|
|
static char sdp[65000];
|
|
char check[6], *type;
|
|
int i = 0, o = 0;
|
|
|
|
switch (osmo_cc_address_type(address)) {
|
|
case osmo_cc_session_addrtype_ipv4:
|
|
type = "IP4";
|
|
break;
|
|
case osmo_cc_session_addrtype_ipv6:
|
|
type = "IP6";
|
|
break;
|
|
default:
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Public IP '%s' is not IPv4 nor IPv6, please correct config!\n", address);
|
|
return input;
|
|
}
|
|
|
|
while (*input) {
|
|
if (o == sizeof(sdp) - 1)
|
|
break;
|
|
/* start over when reading CR/LF */
|
|
if (*input < 32) {
|
|
i = 0;
|
|
sdp[o++] = *input++;
|
|
continue;
|
|
}
|
|
/* read string to check */
|
|
check[i++] = *input;
|
|
sdp[o++] = *input++;
|
|
if (i < 5)
|
|
continue;
|
|
check[i] = '\0';
|
|
i = 0; // not required, but just to be safe!
|
|
/* if string does not match, copy rest of the line */
|
|
if (!!strcmp(check, "c=IN ")) {
|
|
while (*input >= 32) {
|
|
if (o == sizeof(sdp) - 1)
|
|
break;
|
|
sdp[o++] = *input++;
|
|
}
|
|
continue;
|
|
}
|
|
/* replace address */
|
|
while (*input >= 32)
|
|
input++;
|
|
if (sizeof(sdp) - o <= strlen(type) + 1 + strlen(address))
|
|
break;
|
|
strcpy(sdp + o, type);
|
|
o += strlen(type);
|
|
sdp[o++] = ' ';
|
|
strcpy(sdp + o, address);
|
|
o += strlen(address);
|
|
}
|
|
|
|
sdp[o] = '\0';
|
|
return sdp;
|
|
}
|
|
|
|
/* authenticate (remote) */
|
|
static int authenticate(sip_endpoint_t *sip_ep, nua_handle_t *nh, sip_t const *sip)
|
|
{
|
|
sip_www_authenticate_t const *authenticate = NULL;
|
|
char const *realm = NULL;
|
|
char const *scheme = NULL;
|
|
int i;
|
|
char *cur;
|
|
char authentication[256] = "";
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "challenge order received\n");
|
|
|
|
if (!sip_ep->authenticate_remote) {
|
|
PDEBUG(DSIP, DEBUG_NOTICE, "No remote authentication enabled, cannot authenticate us towards remote peer.\n");
|
|
return -1;
|
|
}
|
|
if (!sip_ep->auth_user[0]) {
|
|
PDEBUG(DSIP, DEBUG_NOTICE, "No credentials available\n");
|
|
return -1;
|
|
}
|
|
|
|
if (sip->sip_www_authenticate) {
|
|
authenticate = sip->sip_www_authenticate;
|
|
} else if (sip->sip_proxy_authenticate) {
|
|
authenticate = sip->sip_proxy_authenticate;
|
|
} else {
|
|
PDEBUG(DSIP, DEBUG_NOTICE, "No authentication header found\n");
|
|
return -1;
|
|
}
|
|
|
|
scheme = (char const *) authenticate->au_scheme;
|
|
if (authenticate->au_params) {
|
|
for (i = 0; (cur = (char *) authenticate->au_params[i]); i++) {
|
|
if ((realm = strstr(cur, "realm="))) {
|
|
realm += 6;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!scheme || !realm) {
|
|
PDEBUG(DSIP, DEBUG_NOTICE, "No scheme or no realm in authentication header found\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf(authentication, sizeof(authentication) - 1, "%s:%s:%s:%s", scheme, realm, sip_ep->auth_user, sip_ep->auth_password);
|
|
authentication[sizeof(authentication) - 1] = '\0';
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "auth: '%s'\n", authentication);
|
|
|
|
nua_authenticate(nh, /*SIPTAG_EXPIRES_STR("3600"),*/ NUTAG_AUTH(authentication), TAG_END());
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* some simple nonce generator */
|
|
static void generate_nonce(char *result)
|
|
{
|
|
sprintf(result, "%08x", (uint32_t)random());
|
|
result += 8;
|
|
sprintf(result, "%08x", (uint32_t)random());
|
|
result += 8;
|
|
sprintf(result, "%08x", (uint32_t)random());
|
|
result += 8;
|
|
sprintf(result, "%08x", (uint32_t)random());
|
|
}
|
|
|
|
/* check authorization (local) */
|
|
static int check_authorization(sip_authorization_t const *authorization, const char *regstr, const char *check_user, const char *check_pass, const char *check_realm, const char *check_nonce, const char **auth_text)
|
|
{
|
|
int ret = 500;
|
|
*auth_text = "Internal Server Error";
|
|
|
|
char *username = NULL;
|
|
char *realm = NULL;
|
|
char *nonce = NULL;
|
|
char *uri = NULL;
|
|
char *qop = NULL;
|
|
char *cnonce = NULL;
|
|
char *nc = NULL;
|
|
char *response = NULL;
|
|
|
|
int indexnum;
|
|
const char *cur;
|
|
|
|
char temp[256], first_digest[2 * SU_MD5_DIGEST_SIZE + 1], second_digest[2 * SU_MD5_DIGEST_SIZE + 1], third_digest[2 * SU_MD5_DIGEST_SIZE + 1];
|
|
su_md5_t md5_ctx;
|
|
|
|
if (!check_nonce || !check_nonce[0] || !authorization || !authorization->au_params) {
|
|
if (!strcmp(regstr, "REGISTER")) {
|
|
*auth_text = "Unauthorized";
|
|
ret = 401;
|
|
} else {
|
|
*auth_text = "Proxy Authentication Required";
|
|
ret = 407;
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
/* parse header (stolen from freeswitch) */
|
|
for (indexnum = 0; (cur = authorization->au_params[indexnum]); indexnum++) {
|
|
char *var, *val, *p, *work;
|
|
var = val = work = NULL;
|
|
if ((work = strdup(cur))) {
|
|
var = work;
|
|
if ((val = strchr(var, '='))) {
|
|
*val++ = '\0';
|
|
while (*val == '"') {
|
|
*val++ = '\0';
|
|
}
|
|
if ((p = strchr(val, '"'))) {
|
|
*p = '\0';
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Found in Auth header: %s = %s\n", var, val);
|
|
if (!strcasecmp(var, "username")) {
|
|
username = strdup(val);
|
|
} else if (!strcasecmp(var, "realm")) {
|
|
realm = strdup(val);
|
|
} else if (!strcasecmp(var, "nonce")) {
|
|
nonce = strdup(val);
|
|
} else if (!strcasecmp(var, "uri")) {
|
|
uri = strdup(val);
|
|
} else if (!strcasecmp(var, "qop")) {
|
|
qop = strdup(val);
|
|
} else if (!strcasecmp(var, "cnonce")) {
|
|
cnonce = strdup(val);
|
|
} else if (!strcasecmp(var, "response")) {
|
|
response = strdup(val);
|
|
} else if (!strcasecmp(var, "nc")) {
|
|
nc = strdup(val);
|
|
}
|
|
}
|
|
|
|
free(work);
|
|
}
|
|
}
|
|
|
|
if (!username || !realm || !nonce || ! uri || !response) {
|
|
*auth_text = "Authorization header incomplete";
|
|
ret = 400;
|
|
goto end;
|
|
}
|
|
|
|
if (!!strcmp(username, check_user)) {
|
|
*auth_text = "Authorization Username Missmatch";
|
|
ret = 403;
|
|
goto end;
|
|
}
|
|
if (!!strcmp(realm, check_realm)) {
|
|
*auth_text = "Authorization Realm Missmatch";
|
|
ret = 403;
|
|
goto end;
|
|
}
|
|
if (!!strcmp(nonce, check_nonce)) {
|
|
*auth_text = "Authorization Nonce Missmatch";
|
|
ret = 403;
|
|
goto end;
|
|
}
|
|
|
|
/* perform hash */
|
|
snprintf(temp, sizeof(temp) - 1, "%s:%s:%s", check_user, realm, check_pass);
|
|
temp[sizeof(temp) - 1] = '\0';
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "First hash: %s\n", temp);
|
|
su_md5_init(&md5_ctx);
|
|
su_md5_strupdate(&md5_ctx, temp);
|
|
su_md5_hexdigest(&md5_ctx, first_digest);
|
|
su_md5_deinit(&md5_ctx);
|
|
|
|
snprintf(temp, sizeof(temp) - 1, "%s:%s", regstr, uri);
|
|
temp[sizeof(temp) - 1] = '\0';
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Second hash: %s\n", temp);
|
|
su_md5_init(&md5_ctx);
|
|
su_md5_strupdate(&md5_ctx, temp);
|
|
su_md5_hexdigest(&md5_ctx, second_digest);
|
|
su_md5_deinit(&md5_ctx);
|
|
|
|
if (nc && cnonce && qop)
|
|
snprintf(temp, sizeof(temp) - 1, "%s:%s:%s:%s:%s:%s", first_digest, nonce, nc, cnonce, qop, second_digest);
|
|
else
|
|
snprintf(temp, sizeof(temp) - 1, "%s:%s:%s", first_digest, nonce, second_digest);
|
|
temp[sizeof(temp) - 1] = '\0';
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Third hash: %s\n", temp);
|
|
su_md5_init(&md5_ctx);
|
|
su_md5_strupdate(&md5_ctx, temp);
|
|
su_md5_hexdigest(&md5_ctx, third_digest);
|
|
su_md5_deinit(&md5_ctx);
|
|
|
|
if (!!strcmp(response, third_digest)) {
|
|
*auth_text = "Authorization Failed";
|
|
ret = 403;
|
|
goto end;
|
|
}
|
|
|
|
*auth_text = "Authorization Success";
|
|
ret = 200;
|
|
|
|
end:
|
|
free(username);
|
|
free(realm);
|
|
free(nonce);
|
|
free(uri);
|
|
free(qop);
|
|
free(cnonce);
|
|
free(nc);
|
|
free(response);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void release_and_destroy(call_t *call, uint8_t cc_isdn_cause, uint16_t cc_sip_cause, uint8_t isdn_cause, uint16_t sip_cause, const char *sip_cause_text)
|
|
{
|
|
char isdn_cause_str[256] = "", sip_cause_str[256] = "";
|
|
osmo_cc_msg_t *msg;
|
|
|
|
if (isdn_cause) {
|
|
sprintf(isdn_cause_str, "Q.850;cause=%d;text=\"%s\"", isdn_cause, "");
|
|
}
|
|
if (sip_cause) {
|
|
sprintf(sip_cause_str, "SIP;cause=%d;text=\"%s\"", sip_cause, "");
|
|
}
|
|
|
|
if (cc_isdn_cause || cc_sip_cause) {
|
|
/* create osmo-cc message */
|
|
if (call->state == SIP_STATE_OUT_RELEASE)
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_CNF);
|
|
else
|
|
if (call->state == SIP_STATE_IDLE)
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND);
|
|
else
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
|
|
/* cause */
|
|
osmo_cc_add_ie_cause(msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, cc_isdn_cause, cc_sip_cause, 0);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
}
|
|
|
|
if (call->nua_handle && (isdn_cause || sip_cause)) {
|
|
if (call->state == SIP_STATE_IN_INVITE) {
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", sip_cause, sip_cause_text, call->cc_callref);
|
|
nua_respond(call->nua_handle, (sip_cause < 300) ? 486 : sip_cause, sip_cause_text, // if no usable sip_cause, use 486 (Busy Here)
|
|
TAG_IF(isdn_cause_str[0], SIPTAG_REASON_STR(isdn_cause_str)),
|
|
TAG_END());
|
|
} else
|
|
if (call->state == SIP_STATE_OUT_INVITE) {
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending CANCEL (callref %d)\n", call->cc_callref);
|
|
nua_cancel(call->nua_handle,
|
|
TAG_IF(sip_cause_str[0], SIPTAG_REASON_STR(sip_cause_str)),
|
|
TAG_IF(isdn_cause_str[0], SIPTAG_REASON_STR(isdn_cause_str)),
|
|
TAG_END());
|
|
return;
|
|
} else {
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending BYE (callref %d)\n", call->cc_callref);
|
|
nua_bye(call->nua_handle,
|
|
TAG_IF(sip_cause_str[0], SIPTAG_REASON_STR(sip_cause_str)),
|
|
TAG_IF(isdn_cause_str[0], SIPTAG_REASON_STR(isdn_cause_str)),
|
|
TAG_END());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (call->nua_handle) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", call->nua_handle);
|
|
nua_handle_destroy(call->nua_handle);
|
|
call->nua_handle = NULL;
|
|
}
|
|
|
|
/* call terminated */
|
|
new_state(call, SIP_STATE_IDLE);
|
|
call_destroy(call);
|
|
}
|
|
|
|
/*
|
|
* messages from from CC
|
|
*/
|
|
|
|
static void setup_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
char from[256] = "";
|
|
char asserted_id[256] = "", asserted_msg[512] = "";
|
|
char to[256] = "";
|
|
char contact[256 + 10] = "";
|
|
sip_cseq_t *cseq = NULL;
|
|
uint8_t type, plan, present, screen;
|
|
char callerid[256], dialing[256];
|
|
const char *sdp = sdp;
|
|
int rc;
|
|
|
|
if (!call->sip_ep->remote_peer[0]) {
|
|
PDEBUG(DSIP, DEBUG_NOTICE, "No remote peer set or no peer has registered to us.\n");
|
|
release_and_destroy(call, OSMO_CC_ISDN_CAUSE_DEST_OOO, 0, 0, 0, "");
|
|
return;
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE (callref %d)\n", call->cc_callref);
|
|
|
|
call->nua_handle = nua_handle(call->sip_ep->nua, NULL, TAG_END());
|
|
if (!call->nua_handle) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Failed to create handle\n");
|
|
release_and_destroy(call, OSMO_CC_ISDN_CAUSE_TEMP_FAILURE, 0, 0, 0, "");
|
|
return;
|
|
}
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> new nua_handle %p\n", call->nua_handle);
|
|
|
|
/* caller information */
|
|
rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, callerid, sizeof(callerid));
|
|
if (rc < 0)
|
|
callerid[0] = '\0';
|
|
if (callerid[0] || call->sip_ep->local_user) {
|
|
sprintf(from, "sip:%s@%s", (call->sip_ep->local_user) ? : callerid, call->sip_ep->local_peer);
|
|
if (call->sip_ep->public_ip[0])
|
|
sprintf(contact, "sip:%s@%s", (call->sip_ep->local_user) ? : callerid, call->sip_ep->public_ip);
|
|
} else {
|
|
sprintf(from, "sip:%s", call->sip_ep->local_peer);
|
|
if (call->sip_ep->public_ip[0])
|
|
sprintf(contact, "sip:%s", call->sip_ep->public_ip);
|
|
}
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> From = %s\n", from);
|
|
|
|
/* dialing information */
|
|
rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing));
|
|
if (rc < 0)
|
|
dialing[0] = '\0';
|
|
if (dialing[0] || call->sip_ep->remote_user) {
|
|
sprintf(to, "sip:%s@%s", (call->sip_ep->remote_user) ? : dialing, call->sip_ep->remote_peer);
|
|
} else
|
|
sprintf(to, "sip:%s", call->sip_ep->remote_peer);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> To = %s\n", to);
|
|
|
|
/* asserted id */
|
|
if (call->sip_ep->asserted_id) {
|
|
sprintf(asserted_id, "sip:%s@%s", call->sip_ep->asserted_id, call->sip_ep->local_peer);
|
|
sprintf(asserted_msg, "P-Asserted-Identity: <%s>", asserted_id);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Asserted ID = %s\n", asserted_id);
|
|
}
|
|
|
|
/* public (or stun) ip */
|
|
if (call->sip_ep->public_ip[0]) {
|
|
const char *p;
|
|
// contact is set above
|
|
/* append port of local peer */
|
|
p = osmo_cc_port_of_address(call->sip_ep->local_peer);
|
|
if (p)
|
|
strcat(contact, p);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Contact = %s\n", contact);
|
|
}
|
|
|
|
/* SDP */
|
|
char sdp_buffer[65536];
|
|
rc = osmo_cc_get_ie_sdp(msg, 0, sdp_buffer, sizeof(sdp_buffer));
|
|
if (rc >= 0) {
|
|
sdp = sdp_buffer;
|
|
if (call->sip_ep->public_ip[0]) {
|
|
sdp = sdp_replace_contact(sdp, call->sip_ep->public_ip);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Modify Contact line(s) of SDP:\n");
|
|
}
|
|
free(call->sdp_request);
|
|
call->sdp_request = strdup(sdp);
|
|
osmo_cc_debug_sdp(sdp);
|
|
} else
|
|
sdp = NULL;
|
|
|
|
// cseq = sip_cseq_create(sip_home, 123, SIP_METHOD_INVITE);
|
|
|
|
nua_invite(call->nua_handle,
|
|
TAG_IF(from[0], SIPTAG_FROM_STR(from)),
|
|
TAG_IF(to[0], SIPTAG_TO_STR(to)),
|
|
TAG_IF(asserted_msg[0], SIPTAG_HEADER_STR(asserted_msg)),
|
|
TAG_IF(contact[0], SIPTAG_CONTACT_STR(contact)),
|
|
TAG_IF(cseq, SIPTAG_CSEQ(cseq)),
|
|
NUTAG_MEDIA_ENABLE(0),
|
|
|
|
TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
|
|
TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)),
|
|
TAG_END());
|
|
|
|
new_state(call, SIP_STATE_OUT_INVITE);
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
}
|
|
|
|
static void send_progress_sdp(call_t *call, const char *sdp)
|
|
{
|
|
if (call->sdp_sent)
|
|
return;
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Reply with 183 'Session Progress'\n");
|
|
|
|
if (call->sip_ep->public_ip[0]) {
|
|
sdp = sdp_replace_contact(sdp, call->sip_ep->public_ip);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Modify Contact line(s) of SDP:\n");
|
|
}
|
|
free(call->sdp_response);
|
|
call->sdp_response = strdup(sdp);
|
|
osmo_cc_debug_sdp(sdp);
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_183_SESSION_PROGRESS, call->cc_callref);
|
|
|
|
nua_respond(call->nua_handle, SIP_183_SESSION_PROGRESS,
|
|
NUTAG_MEDIA_ENABLE(0),
|
|
TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
|
|
TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)),
|
|
TAG_END());
|
|
call->sdp_sent = 1;
|
|
}
|
|
|
|
static void setup_ack_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
char sdp[65536];
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp));
|
|
if (rc >= 0)
|
|
send_progress_sdp(call, sdp);
|
|
}
|
|
|
|
static void proc_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
char sdp[65536];
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp));
|
|
if (rc >= 0)
|
|
send_progress_sdp(call, sdp);
|
|
}
|
|
|
|
static void alert_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
char sdp[65536];
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp));
|
|
if (rc >= 0)
|
|
send_progress_sdp(call, sdp);
|
|
|
|
if (call->sip_ep->send_no_ringing_after_progress) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Sending no 180 'Ringing' after 183 'Session Progress' with SDP.\n");
|
|
return;
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_180_RINGING, call->cc_callref);
|
|
|
|
nua_respond(call->nua_handle, SIP_180_RINGING, TAG_END());
|
|
}
|
|
|
|
static void setup_rsp(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
char sdp_buffer[65536];
|
|
const char *sdp;
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_sdp(msg, 0, sdp_buffer, sizeof(sdp_buffer));
|
|
if (rc >= 0) {
|
|
sdp = sdp_buffer;
|
|
if (call->sip_ep->public_ip[0]) {
|
|
sdp = sdp_replace_contact(sdp, call->sip_ep->public_ip);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Modify Contact line(s) of SDP:\n");
|
|
}
|
|
free(call->sdp_response);
|
|
call->sdp_response = strdup(sdp);
|
|
osmo_cc_debug_sdp(sdp);
|
|
} else
|
|
sdp = NULL;
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref);
|
|
|
|
nua_respond(call->nua_handle, SIP_200_OK,
|
|
NUTAG_MEDIA_ENABLE(0),
|
|
TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
|
|
TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)),
|
|
TAG_END());
|
|
|
|
new_state(call, SIP_STATE_CONNECT);
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
}
|
|
|
|
static void progress_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
char sdp[65536];
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp));
|
|
if (rc >= 0)
|
|
send_progress_sdp(call, sdp);
|
|
}
|
|
|
|
static void info_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
uint8_t duration_ms, pause_ms, dtmf_mode;
|
|
char digits[256];
|
|
char dtmf_str[256];
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_dtmf(msg, 0, &duration_ms, &pause_ms, &dtmf_mode, digits, sizeof(digits));
|
|
if (rc >= 0 && digits[0]) {
|
|
/* prepare DTMF info payload */
|
|
sprintf(dtmf_str, "Signal=%c\r\nDuration=%d\r\n", digits[0], duration_ms);
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INFO (callref %d)\n", call->cc_callref);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> DTMF digit %c\n", digits[0]);
|
|
|
|
/* start invite to handle DTMF */
|
|
nua_info(call->nua_handle,
|
|
NUTAG_MEDIA_ENABLE(0),
|
|
SIPTAG_CONTENT_TYPE_STR("application/dtmf-relay"),
|
|
SIPTAG_PAYLOAD_STR(dtmf_str), TAG_END());
|
|
}
|
|
|
|
}
|
|
|
|
static void notify_req(void)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_NOTICE, "CC-NOTIFY-REQUEST not supported!\n");
|
|
}
|
|
|
|
static void rej_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
uint8_t location, isdn_cause, socket_cause;
|
|
uint16_t sip_cause;
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
|
if (rc < 0) {
|
|
location = OSMO_CC_LOCATION_BEYOND_INTERWORKING;
|
|
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
|
sip_cause = 486;
|
|
}
|
|
|
|
release_and_destroy(call, 0, 0, isdn_cause, sip_cause, "");
|
|
}
|
|
|
|
static void disc_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
uint8_t location, isdn_cause, socket_cause;
|
|
uint16_t sip_cause;
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
|
if (rc < 0) {
|
|
location = OSMO_CC_LOCATION_BEYOND_INTERWORKING;
|
|
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
|
sip_cause = 486;
|
|
}
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
|
|
/* cause */
|
|
osmo_cc_add_ie_cause(msg, location, isdn_cause, sip_cause, 0);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
|
|
release_and_destroy(call, 0, 0, isdn_cause, sip_cause, "");
|
|
}
|
|
|
|
static void rel_req(call_t *call, osmo_cc_msg_t *msg)
|
|
{
|
|
uint8_t location, isdn_cause, socket_cause;
|
|
uint16_t sip_cause;
|
|
int rc;
|
|
|
|
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
|
if (rc < 0) {
|
|
location = OSMO_CC_LOCATION_BEYOND_INTERWORKING;
|
|
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
|
sip_cause = 486;
|
|
}
|
|
|
|
new_state(call, SIP_STATE_OUT_RELEASE);
|
|
|
|
release_and_destroy(call, 0, 0, isdn_cause, sip_cause, "");
|
|
}
|
|
|
|
void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg)
|
|
{
|
|
sip_endpoint_t *sip_ep = ep->priv;
|
|
call_t *call;
|
|
|
|
/* hunt for callref */
|
|
call = sip_ep->call_list;
|
|
while (call) {
|
|
if (call->cc_callref == callref)
|
|
break;
|
|
call = call->next;
|
|
}
|
|
|
|
/* process SETUP */
|
|
if (!call) {
|
|
if (msg->type != OSMO_CC_MSG_SETUP_REQ) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "received message without call instance, please fix!\n");
|
|
return;
|
|
}
|
|
/* creating call instance, transparent until setup with hdlc */
|
|
call = call_create(sip_ep);
|
|
if (!call) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Cannot create calll instance.\n");
|
|
abort();
|
|
}
|
|
/* link with cc */
|
|
call->cc_callref = callref;
|
|
}
|
|
|
|
switch (msg->type) {
|
|
case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */
|
|
setup_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */
|
|
if (call->state != SIP_STATE_IN_INVITE)
|
|
break;
|
|
setup_ack_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */
|
|
if (call->state != SIP_STATE_IN_INVITE)
|
|
break;
|
|
proc_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */
|
|
if (call->state != SIP_STATE_IN_INVITE)
|
|
break;
|
|
alert_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */
|
|
if (call->state != SIP_STATE_IN_INVITE)
|
|
break;
|
|
setup_rsp(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_SETUP_COMP_REQ: /* call of endpoint is connected */
|
|
break;
|
|
|
|
case OSMO_CC_MSG_PROGRESS_REQ: /* progress */
|
|
if (call->state != SIP_STATE_IN_INVITE)
|
|
break;
|
|
progress_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_INFO_REQ: /* overlap dialing */
|
|
if (call->state != SIP_STATE_CONNECT)
|
|
break;
|
|
info_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_NOTIFY_REQ: /* display and notifications */
|
|
if (call->state != SIP_STATE_CONNECT)
|
|
break;
|
|
notify_req();
|
|
break;
|
|
|
|
case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */
|
|
rej_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */
|
|
disc_req(call, msg);
|
|
break;
|
|
|
|
case OSMO_CC_MSG_REL_REQ: /* release sip call port */
|
|
rel_req(call, msg);
|
|
break;
|
|
|
|
default:
|
|
PDEBUG(DSIP, DEBUG_ERROR, "received an unsupported CC message: %d\n", msg->type);
|
|
}
|
|
|
|
osmo_cc_free_msg(msg);
|
|
}
|
|
|
|
/*
|
|
* messages from SIP stack
|
|
*/
|
|
|
|
static void ep_i_register(sip_endpoint_t *sip_ep, int status, nua_t *nua, nua_handle_t *nh, sip_t const *sip)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received REGISTER (registration)\n");
|
|
|
|
#define NUTAG_WITH_THIS_MSG(msg) nutag_with, tag_ptr_v(msg)
|
|
nua_saved_event_t saved[1];
|
|
nua_save_event(nua, saved);
|
|
nua_event_data_t const *data = nua_event_data(saved);
|
|
sip_authorization_t const *authorization;
|
|
char contact[255] = "";
|
|
const char *auth_text = NULL;
|
|
char auth_str[256] = "";
|
|
|
|
if (sip->sip_contact->m_url->url_host) {
|
|
// FIXME: unstable/might not work with IPv6
|
|
strcpy(contact, sip->sip_contact->m_url->url_host);
|
|
if (sip->sip_contact->m_url->url_port && sip->sip_contact->m_url->url_port[0]) {
|
|
strcat(contact, ":");
|
|
strcat(contact, sip->sip_contact->m_url->url_port);
|
|
}
|
|
}
|
|
|
|
if (!sip_ep->local_register) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "forbidden, because we don't accept registration");
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending REGISTER response: %d %s (registration)\n", SIP_403_FORBIDDEN);
|
|
nua_respond(nh, SIP_403_FORBIDDEN, NUTAG_WITH_THIS_MSG(data->e_msg), TAG_END());
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh);
|
|
nua_handle_destroy(nh);
|
|
sip_ep->register_handle = NULL;
|
|
return;
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> contact %s\n", contact);
|
|
|
|
if (sip_ep->authenticate_local && sip_ep->auth_realm[0]) {
|
|
authorization = sip->sip_authorization;
|
|
status = check_authorization(authorization, "REGISTER", sip_ep->auth_user, sip_ep->auth_password, sip_ep->auth_realm, sip_ep->register_nonce, &auth_text);
|
|
if (status == 401) {
|
|
if (!sip_ep->register_nonce[0])
|
|
generate_nonce(sip_ep->register_nonce);
|
|
sprintf(auth_str, "Digest realm=\"%s\", nonce=\"%s\", algorithm=MD5, qop=\"auth\"", sip_ep->auth_realm, sip_ep->register_nonce);
|
|
}
|
|
} else {
|
|
status = 200;
|
|
auth_text = "Authentication not required";
|
|
}
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Authentication: %d %s\n", status, auth_text);
|
|
|
|
if (status == 200) {
|
|
strncpy(sip_ep->remote_contact, contact, sizeof(sip_ep->remote_contact) - 1);
|
|
sip_ep->remote_peer = sip_ep->remote_contact;
|
|
sip_ep->register_nonce[0] = '\0';
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending REGISTER response: %d %s (registration)\n", status, auth_text);
|
|
|
|
nua_respond(nh, status, auth_text, SIPTAG_CONTACT(sip->sip_contact), NUTAG_WITH_THIS_MSG(data->e_msg), TAG_IF(auth_str[0], SIPTAG_WWW_AUTHENTICATE_STR(auth_str)), TAG_END());
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh);
|
|
nua_handle_destroy(nh);
|
|
sip_ep->register_handle = NULL;
|
|
}
|
|
|
|
static void ep_r_register(sip_endpoint_t *sip_ep, int status, char const *phrase, nua_handle_t *nh, sip_t const *sip)
|
|
{
|
|
int rc;
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received REGISTER response: %d %s (registration)\n", status, phrase);
|
|
|
|
switch (status) {
|
|
case 200:
|
|
status_200:
|
|
/* if not registered, become registered and start register interval timer */
|
|
if (sip_ep->register_state != REGISTER_STATE_REGISTERED) {
|
|
if (sip_ep->register_interval)
|
|
timer_start(&sip_ep->register_retry_timer, sip_ep->register_interval);
|
|
sip_ep->register_state = REGISTER_STATE_REGISTERED;
|
|
}
|
|
#if 0
|
|
//register options does not work
|
|
/* start option timer */
|
|
if (sip_ep->options_interval) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Register ok, scheduling option timer with %d seconds\n", sip_ep->options_interval);
|
|
timer_start(&sip_ep->register_option_timer, sip_ep->options_interval);
|
|
}
|
|
#endif
|
|
break;
|
|
case 401:
|
|
case 407:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Register challenge received\n");
|
|
rc = authenticate(sip_ep, nh, sip);
|
|
if (rc < 0)
|
|
goto status_400;
|
|
break;
|
|
default:
|
|
if (status >= 200 && status <= 299)
|
|
goto status_200;
|
|
if (status < 400)
|
|
break;
|
|
status_400:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Register failed, starting register timer\n");
|
|
sip_ep->register_state = REGISTER_STATE_FAILED;
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh);
|
|
nua_handle_destroy(nh);
|
|
sip_ep->register_handle = NULL;
|
|
/* stop option timer */
|
|
timer_stop(&sip_ep->register_option_timer);
|
|
/* if failed, start register interval timer with REGISTER_RETRY_TIMER */
|
|
timer_start(&sip_ep->register_retry_timer, REGISTER_RETRY_TIMER);
|
|
}
|
|
}
|
|
|
|
static void ep_i_options(nua_t *nua, nua_handle_t *nh)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS (registration)\n");
|
|
|
|
#define NUTAG_WITH_THIS_MSG(msg) nutag_with, tag_ptr_v(msg)
|
|
nua_saved_event_t saved[1];
|
|
nua_save_event(nua, saved);
|
|
nua_event_data_t const *data = nua_event_data(saved);
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS response: %d %s (registration)\n", SIP_200_OK);
|
|
|
|
nua_respond(nh, SIP_200_OK, NUTAG_WITH_THIS_MSG(data->e_msg), TAG_END());
|
|
}
|
|
|
|
static void ep_r_options(sip_endpoint_t __attribute__((unused)) *sip_ep, int status, char const *phrase, nua_handle_t __attribute__((unused)) *nh)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS response: %d %s (registration)\n", status, phrase);
|
|
|
|
#if 0
|
|
//register options does not work
|
|
if (status >= 200 && status <= 299) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "options ok, scheduling option timer with %d seconds\n", sip_ep->options_interval);
|
|
/* restart option timer */
|
|
timer_start(&sip_ep->register_option_timer, sip_ep->options_interval);
|
|
return;
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Register options failed, starting register timer\n");
|
|
sip_ep->register_state = REGISTER_STATE_FAILED;
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh);
|
|
nua_handle_destroy(nh);
|
|
sip_ep->register_handle = NULL;
|
|
/* if failed, start register interval timer with REGISTER_RETRY_TIMER */
|
|
timer_start(&sip_ep->register_retry_timer, REGISTER_RETRY_TIMER);
|
|
#endif
|
|
}
|
|
|
|
static void call_i_invite(call_t *call, nua_handle_t *nh, sip_t const *sip)
|
|
{
|
|
const char *from = "", *to = "", *from_name = "", *to_name = "";
|
|
sip_authorization_t const *authorization;
|
|
const char *auth_text = NULL;
|
|
char auth_str[256] = "";
|
|
osmo_cc_msg_t *msg;
|
|
int status;
|
|
|
|
if (sip->sip_from) {
|
|
if (sip->sip_from->a_url && sip->sip_from->a_url->url_user)
|
|
from = sip->sip_from->a_url->url_user;
|
|
if (sip->sip_from->a_display)
|
|
from_name = sip->sip_from->a_display;
|
|
}
|
|
if (sip->sip_to) {
|
|
if (sip->sip_to->a_url && sip->sip_to->a_url->url_user)
|
|
to = sip->sip_to->a_url->url_user;
|
|
if (sip->sip_to->a_display)
|
|
to_name = sip->sip_to->a_display;
|
|
}
|
|
|
|
if (call->state != SIP_STATE_IDLE)
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received RE-INVITE (callref %d)\n", call->cc_callref);
|
|
else
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received INVITE (callref %d)\n", call->cc_callref);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> From = %s '%s'\n", from, from_name);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> To = %s '%s'\n", to, to_name);
|
|
|
|
if (call->sip_ep->authenticate_local && call->sip_ep->auth_realm[0] && call->state == SIP_STATE_IDLE) {
|
|
/* only authenticate remote, if we don't have a re-invite */
|
|
authorization = sip->sip_proxy_authorization;
|
|
status = check_authorization(authorization, "INVITE", call->sip_ep->auth_user, call->sip_ep->auth_password, call->sip_ep->auth_realm, call->sip_ep->invite_nonce, &auth_text);
|
|
if (status == 407) {
|
|
if (!call->sip_ep->invite_nonce[0])
|
|
generate_nonce(call->sip_ep->invite_nonce);
|
|
sprintf(auth_str, "Digest realm=\"%s\", nonce=\"%s\", algorithm=MD5, qop=\"auth\"", call->sip_ep->auth_realm, call->sip_ep->invite_nonce);
|
|
}
|
|
} else {
|
|
status = 200;
|
|
auth_text = "Authentication not required";
|
|
}
|
|
|
|
if (status != 200) {
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", status, auth_text, call->cc_callref);
|
|
nua_respond(nh, status, auth_text, SIPTAG_CONTACT(sip->sip_contact), TAG_IF(auth_str[0], SIPTAG_PROXY_AUTHENTICATE_STR(auth_str)), TAG_END());
|
|
nua_handle_destroy(nh);
|
|
call_destroy(call);
|
|
return;
|
|
}
|
|
call->sip_ep->invite_nonce[0] = '\0';
|
|
|
|
if (sip->sip_payload && sip->sip_payload->pl_data && sip->sip_payload->pl_len) {
|
|
free(call->sdp_request);
|
|
call->sdp_request = malloc(sip->sip_payload->pl_len + 1);
|
|
memcpy(call->sdp_request, sip->sip_payload->pl_data, sip->sip_payload->pl_len);
|
|
call->sdp_request[sip->sip_payload->pl_len] = '\0';
|
|
} else {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> No SDP in message\n");
|
|
new_state(call, SIP_STATE_IN_INVITE);
|
|
release_and_destroy(call, 0, 400, 0, SIP_400_BAD_REQUEST);
|
|
return;
|
|
}
|
|
|
|
/* handle re-invite */
|
|
if (call->state != SIP_STATE_IDLE) {
|
|
char *sdp = call->sdp_response;
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending RE-INVITE response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref);
|
|
nua_respond(call->nua_handle, SIP_200_OK,
|
|
NUTAG_MEDIA_ENABLE(0),
|
|
TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
|
|
TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)),
|
|
TAG_END());
|
|
return;
|
|
}
|
|
|
|
|
|
/* apply handle */
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "new nua_handle %p\n", nh);
|
|
call->nua_handle = nh;
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND);
|
|
|
|
/* newtwork + interface */
|
|
osmo_cc_add_ie_calling_network(msg, OSMO_CC_NETWORK_SIP_NONE, "");
|
|
osmo_cc_add_ie_calling_interface(msg, call->sip_ep->name);
|
|
|
|
|
|
if (call->sdp_request) {
|
|
/* send SDP answer */
|
|
osmo_cc_add_ie_sdp(msg, call->sdp_request);
|
|
}
|
|
|
|
/* caller information */
|
|
if (!from[0]) {
|
|
osmo_cc_add_ie_calling(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_NOT_AVAIL, OSMO_CC_SCREEN_NETWORK, "");
|
|
} else {
|
|
osmo_cc_add_ie_calling(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, from);
|
|
if (from_name[0])
|
|
osmo_cc_add_ie_calling_name(msg, from_name);
|
|
}
|
|
|
|
/* dialing information */
|
|
if (to[0]) {
|
|
osmo_cc_add_ie_called(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, to);
|
|
if (to_name[0])
|
|
osmo_cc_add_ie_called_name(msg, to_name);
|
|
}
|
|
|
|
/* complete */
|
|
osmo_cc_add_ie_complete(msg);
|
|
|
|
#ifdef NUTAG_AUTO100
|
|
/* send trying (proceeding) */
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_100_TRYING, call->cc_callref);
|
|
nua_respond(nh, SIP_100_TRYING, TAG_END());
|
|
#endif
|
|
|
|
/* create endpoint */
|
|
osmo_cc_call_t *cc_call = osmo_cc_call_new(&call->sip_ep->cc_ep);
|
|
call->cc_callref = cc_call->callref;
|
|
|
|
new_state(call, SIP_STATE_IN_INVITE);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
|
|
/* start option timer */
|
|
if (call->sip_ep->options_interval) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Invite received, scheduling option timer with %d seconds\n", call->sip_ep->options_interval);
|
|
timer_start(&call->invite_option_timer, call->sip_ep->options_interval);
|
|
}
|
|
}
|
|
|
|
static void call_r_invite(call_t *call, int status, char const *phrase, nua_handle_t *nh, sip_t const *sip)
|
|
{
|
|
osmo_cc_msg_t *msg;
|
|
int rc;
|
|
|
|
if (call->state == SIP_STATE_CONNECT)
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received RE-INVITE response: %d %s (callref %d)\n", status, phrase, call->cc_callref);
|
|
else
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received INVITE response: %d %s (callref %d)\n", status, phrase, call->cc_callref);
|
|
|
|
if (status == 401 || status == 407) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Invite challenge received\n");
|
|
rc = authenticate(call->sip_ep, nh, sip);
|
|
if (rc < 0) {
|
|
release_and_destroy(call, 0, status, 0, 0, "");
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* connect audio */
|
|
if (status == 183 || (status >= 200 && status <= 299)) {
|
|
if (sip->sip_payload && sip->sip_payload->pl_data && sip->sip_payload->pl_len) {
|
|
free(call->sdp_response);
|
|
call->sdp_response = malloc(sip->sip_payload->pl_len + 1);
|
|
memcpy(call->sdp_response, sip->sip_payload->pl_data, sip->sip_payload->pl_len);
|
|
call->sdp_response[sip->sip_payload->pl_len] = '\0';
|
|
osmo_cc_debug_sdp(call->sdp_response);
|
|
} else if (status >= 200 && status <= 299) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> No SDP in message\n");
|
|
release_and_destroy(call, 0, 400, 0, SIP_400_BAD_REQUEST);
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (status) {
|
|
case 180:
|
|
if (call->alerting_sent)
|
|
return;
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
return;
|
|
case 183:
|
|
/* create osmo-cc message */
|
|
if (call->sip_ep->receive_no_ringing_after_progress) {
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND);
|
|
call->alerting_sent = 1;
|
|
} else
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_PROGRESS_IND);
|
|
|
|
if (call->sdp_response) {
|
|
/* progress indicator */
|
|
osmo_cc_add_ie_progress(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
|
|
|
|
/* send SDP answer */
|
|
osmo_cc_add_ie_sdp(msg, call->sdp_response);
|
|
}
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
return;
|
|
case 200:
|
|
status_200:
|
|
nua_ack(nh, TAG_END());
|
|
|
|
/* on reinvite we are done */
|
|
if (call->state == SIP_STATE_CONNECT)
|
|
return;
|
|
|
|
/* start option timer */
|
|
if (call->sip_ep->options_interval) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Invite response, scheduling option timer with %d seconds\n", call->sip_ep->options_interval);
|
|
timer_start(&call->invite_option_timer, call->sip_ep->options_interval);
|
|
}
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF);
|
|
|
|
if (call->sdp_response) {
|
|
/* send SDP answer */
|
|
osmo_cc_add_ie_sdp(msg, call->sdp_response);
|
|
}
|
|
|
|
new_state(call, SIP_STATE_CONNECT);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
return;
|
|
default:
|
|
if (status >= 200 && status <= 299)
|
|
goto status_200;
|
|
if (status < 100 || status > 199)
|
|
break;
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "skipping 1xx message\n");
|
|
|
|
return;
|
|
}
|
|
|
|
release_and_destroy(call, 0, status, 0, 0, "");
|
|
}
|
|
|
|
static void call_i_options(call_t *call, nua_handle_t *nh)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS (callref %d)\n", call->cc_callref);
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref);
|
|
|
|
nua_respond(nh, SIP_200_OK, TAG_END());
|
|
}
|
|
|
|
static void call_r_options(call_t *call, int status, char const *phrase)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS response: %d %s (callref %d)\n", status, phrase, call->cc_callref);
|
|
|
|
if (status >= 200 && status <= 299) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "options ok, scheduling option timer with %d seconds\n", call->sip_ep->options_interval);
|
|
/* restart option timer */
|
|
timer_start(&call->invite_option_timer, call->sip_ep->options_interval);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// code stolen from freeswitch....
|
|
static char RFC2833_CHARS[] = "0123456789*#ABCDF";
|
|
|
|
static char switch_rfc2833_to_char(int event)
|
|
{
|
|
if (event > -1 && event < (int32_t) sizeof(RFC2833_CHARS)) {
|
|
return RFC2833_CHARS[event];
|
|
}
|
|
return '\0';
|
|
}
|
|
|
|
static void call_i_info(call_t *call, nua_t *nua, nua_handle_t *nh, sip_t const *sip)
|
|
{
|
|
char digit = '\0';
|
|
osmo_cc_msg_t *msg;
|
|
|
|
#define NUTAG_WITH_THIS_MSG(msg) nutag_with, tag_ptr_v(msg)
|
|
nua_saved_event_t saved[1];
|
|
nua_save_event(nua, saved);
|
|
nua_event_data_t const *data = nua_event_data(saved);
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received INFO (callref %d)\n", call->cc_callref);
|
|
|
|
// code stolen from freeswitch....
|
|
|
|
if (sip && sip->sip_content_type && sip->sip_content_type->c_type && sip->sip_content_type->c_subtype && sip->sip_payload && sip->sip_payload->pl_data) {
|
|
if (!strncasecmp(sip->sip_content_type->c_type, "application", 11) && !strcasecmp(sip->sip_content_type->c_subtype, "dtmf-relay")) {
|
|
const char *signal_ptr;
|
|
if ((signal_ptr = strstr(sip->sip_payload->pl_data, "Signal="))) {
|
|
int tmp;
|
|
/* move signal_ptr where we need it (right past Signal=) */
|
|
signal_ptr = signal_ptr + 7;
|
|
|
|
/* handle broken devices with spaces after the = (cough) VegaStream (cough) */
|
|
while (*signal_ptr && *signal_ptr == ' ')
|
|
signal_ptr++;
|
|
|
|
if (*signal_ptr
|
|
&& (*signal_ptr == '*' || *signal_ptr == '#' || *signal_ptr == 'A' || *signal_ptr == 'B' || *signal_ptr == 'C'
|
|
|| *signal_ptr == 'D')) {
|
|
digit = *signal_ptr;
|
|
} else {
|
|
tmp = atoi(signal_ptr);
|
|
digit = switch_rfc2833_to_char(tmp);
|
|
}
|
|
}
|
|
|
|
} else if (!strncasecmp(sip->sip_content_type->c_type, "application", 11) && !strcasecmp(sip->sip_content_type->c_subtype, "dtmf")) {
|
|
int tmp = atoi(sip->sip_payload->pl_data);
|
|
digit = switch_rfc2833_to_char(tmp);
|
|
}
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INFO response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref);
|
|
|
|
nua_respond(nh, SIP_200_OK, NUTAG_WITH_THIS_MSG(data->e_msg), TAG_END());
|
|
|
|
if (digit) {
|
|
char digits[2] = { digit, '\0' };
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> DTMF digit: %c\n", digit);
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_INFO_IND);
|
|
|
|
/* dtmf */
|
|
osmo_cc_add_ie_dtmf(msg, 160, 160, OSMO_CC_DTMF_MODE_DIGITS, digits);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
}
|
|
}
|
|
|
|
static void call_i_bye(call_t *call, nua_handle_t *nh, sip_t const *sip)
|
|
{
|
|
sip_reason_t *reason;
|
|
uint8_t isdn_cause = 0;
|
|
uint16_t sip_cause = 0;
|
|
osmo_cc_msg_t *msg;
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received BYE (callref %d)\n", call->cc_callref);
|
|
|
|
for (reason = sip->sip_reason; reason; reason = reason->re_next) {
|
|
if (!reason->re_protocol)
|
|
continue;
|
|
if (!isdn_cause && !strcasecmp(sip->sip_reason->re_protocol, "Q.850") && sip->sip_reason->re_cause) {
|
|
isdn_cause = atoi(sip->sip_reason->re_cause);
|
|
PDEBUG(DSIP, DEBUG_INFO, " -> ISDN cause: %d\n", isdn_cause);
|
|
}
|
|
if (!sip_cause && !strcasecmp(sip->sip_reason->re_protocol, "SIP") && sip->sip_reason->re_cause) {
|
|
sip_cause = atoi(sip->sip_reason->re_cause);
|
|
PDEBUG(DSIP, DEBUG_INFO, " -> SIP cause: %d\n", sip_cause);
|
|
}
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending BYE response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref);
|
|
|
|
nua_respond(nh, SIP_200_OK, TAG_END());
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
|
|
/* cause */
|
|
osmo_cc_add_ie_cause(msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, sip_cause, 0);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh);
|
|
nua_handle_destroy(nh);
|
|
call->nua_handle = NULL;
|
|
call_destroy(call);
|
|
}
|
|
|
|
static void call_r_bye(call_t *call, int status, char const *phrase, nua_handle_t *nh)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received BYE response: %d %s (callref %d)\n", status, phrase, call->cc_callref);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh);
|
|
nua_handle_destroy(nh);
|
|
call->nua_handle = NULL;
|
|
call_destroy(call);
|
|
}
|
|
|
|
static void call_i_cancel(call_t *call, nua_handle_t *nh)
|
|
{
|
|
osmo_cc_msg_t *msg;
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received CANCEL (callref %d)\n", call->cc_callref);
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending CANCEL response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref);
|
|
|
|
nua_respond(nh, SIP_200_OK, TAG_END());
|
|
|
|
/* create osmo-cc message */
|
|
msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh);
|
|
nua_handle_destroy(nh);
|
|
call->nua_handle = NULL;
|
|
call_destroy(call);
|
|
}
|
|
|
|
static void call_r_cancel(call_t *call, int status, char const *phrase, nua_handle_t *nh)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_INFO, "Received CANCEL response: %d %s (callref %d)\n", status, phrase, call->cc_callref);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh);
|
|
nua_handle_destroy(nh);
|
|
call->nua_handle = NULL;
|
|
call_destroy(call);
|
|
}
|
|
|
|
static void call_i_state(call_t *call, int status, char const *phrase)
|
|
{
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "State change received: %d %s (callref %d)\n", status, phrase, call->cc_callref);
|
|
}
|
|
|
|
/* messages from SIP stack */
|
|
static void sip_message(nua_event_t event, int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t __attribute__((unused)) *hmagic, sip_t const *sip, tagi_t __attribute__((unused)) tags[])
|
|
{
|
|
sip_endpoint_t *sip_ep = (sip_endpoint_t *)magic;
|
|
call_t *call;
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Event %d from SIP stack received (handle=%p)\n", event, nh);
|
|
if (!nh)
|
|
return;
|
|
|
|
call = sip_ep->call_list;
|
|
while (call) {
|
|
if (call->nua_handle == nh)
|
|
break;
|
|
call = call->next;
|
|
}
|
|
|
|
/* new handle */
|
|
switch (event) {
|
|
case nua_i_register:
|
|
if (!call && !sip_ep->register_handle) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "new nua_handle %p (register)\n", nh);
|
|
sip_ep->register_handle = nh;
|
|
}
|
|
if (!call) {
|
|
ep_i_register(sip_ep, status, nua, nh, sip);
|
|
return;
|
|
}
|
|
break;
|
|
case nua_r_register:
|
|
if (!call) {
|
|
ep_r_register(sip_ep, status, phrase, nh, sip);
|
|
return;
|
|
}
|
|
break;
|
|
case nua_i_options:
|
|
if (!call) {
|
|
ep_i_options(nua, nh);
|
|
if (sip_ep->register_handle != nh) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh);
|
|
nua_handle_destroy(nh);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
case nua_r_options:
|
|
if (!call) {
|
|
ep_r_options(sip_ep, status, phrase, nh);
|
|
return;
|
|
}
|
|
break;
|
|
case nua_i_invite:
|
|
if (!call) {
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "New call instance\n");
|
|
|
|
/* create call instance */
|
|
call = call_create(sip_ep);
|
|
if (!call) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Cannot create call instance.\n");
|
|
abort();
|
|
}
|
|
}
|
|
break;
|
|
case nua_i_outbound:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Outbound status\n");
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
if (!call) {
|
|
/* terminate call, if it does not exist */
|
|
if (nh != sip_ep->register_handle) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "no SIP Port found for handle %p\n", nh);
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s\n", SIP_500_INTERNAL_SERVER_ERROR);
|
|
nua_respond(nh, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh);
|
|
nua_handle_destroy(nh);
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (event) {
|
|
case nua_r_set_params:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "setparam response\n");
|
|
break;
|
|
case nua_i_error:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "error received\n");
|
|
break;
|
|
case nua_i_invite:
|
|
call_i_invite(call, nh, sip);
|
|
break;
|
|
case nua_r_invite:
|
|
call_r_invite(call, status, phrase, nh, sip);
|
|
break;
|
|
case nua_i_ack:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "ack received\n");
|
|
break;
|
|
case nua_i_active:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "active received\n");
|
|
break;
|
|
case nua_i_options:
|
|
call_i_options(call, nh);
|
|
break;
|
|
case nua_r_options:
|
|
call_r_options(call, status, phrase);
|
|
break;
|
|
case nua_i_info:
|
|
call_i_info(call, nua, nh, sip);
|
|
break;
|
|
case nua_i_bye:
|
|
call_i_bye(call, nh, sip);
|
|
break;
|
|
case nua_r_bye:
|
|
call_r_bye(call, status, phrase, nh);
|
|
break;
|
|
case nua_i_cancel:
|
|
call_i_cancel(call, nh);
|
|
break;
|
|
case nua_r_cancel:
|
|
call_r_cancel(call, status, phrase, nh);
|
|
break;
|
|
case nua_i_state:
|
|
call_i_state(call, status, phrase);
|
|
break;
|
|
case nua_i_terminated:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "terminated received\n");
|
|
break;
|
|
default:
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Event %d not handled\n", event);
|
|
}
|
|
}
|
|
|
|
static void stun_bind_cb(stun_discovery_magic_t *magic, stun_handle_t __attribute__((unused)) *sh, stun_discovery_t *sd, stun_action_t __attribute__((unused)) action, stun_state_t event)
|
|
{
|
|
sip_endpoint_t *sip_ep = (sip_endpoint_t *)magic;
|
|
su_sockaddr_t sa;
|
|
socklen_t addrlen;
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "Event %d from STUN stack received\n", event);
|
|
|
|
switch (event) {
|
|
case stun_discovery_done:
|
|
addrlen = sizeof(sa);
|
|
memset(&sa, 0, addrlen);
|
|
if (stun_discovery_get_address(sd, &sa, &addrlen) < 0)
|
|
goto failed;
|
|
su_inet_ntop(sa.su_family, SU_ADDR(&sa), sip_ep->public_ip, sizeof(sip_ep->public_ip));
|
|
sip_ep->stun_state = STUN_STATE_RESOLVED;
|
|
/* start timer for next stun request with sip_ep->stun_interval */
|
|
timer_start(&sip_ep->stun_retry_timer, sip_ep->stun_interval);
|
|
PDEBUG(DSIP, DEBUG_INFO, "STUN resolved!\n");
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Public IP = %s\n", sip_ep->public_ip);
|
|
break;
|
|
default:
|
|
failed:
|
|
PDEBUG(DSIP, DEBUG_INFO, "STUN Resolving failed!\n");
|
|
sip_ep->stun_state = STUN_STATE_FAILED;
|
|
/* start timer for next stun request (after failing) with STUN_RETRY_TIMER */
|
|
timer_start(&sip_ep->stun_retry_timer, STUN_RETRY_TIMER);
|
|
}
|
|
}
|
|
|
|
static void invite_option_timeout(struct timer *timer)
|
|
{
|
|
call_t *call = timer->priv;
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "invite options timer fired\n");
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS (callref %d)\n", call->cc_callref);
|
|
|
|
nua_options(call->nua_handle,
|
|
TAG_END());
|
|
}
|
|
|
|
static void stun_retry_timeout(struct timer *timer)
|
|
{
|
|
sip_endpoint_t *sip_ep = timer->priv;
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "timeout, restart stun lookup\n");
|
|
|
|
sip_ep->stun_state = STUN_STATE_UNRESOLVED;
|
|
}
|
|
|
|
static void register_retry_timeout(struct timer *timer)
|
|
{
|
|
sip_endpoint_t *sip_ep = timer->priv;
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "timeout, restart register\n");
|
|
|
|
/* if we have a handle, destroy it and becom unregistered, so registration is
|
|
* triggered next */
|
|
if (sip_ep->register_handle) {
|
|
/* stop option timer */
|
|
timer_stop(&sip_ep->register_option_timer);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", sip_ep->register_handle);
|
|
nua_handle_destroy(sip_ep->register_handle);
|
|
sip_ep->register_handle = NULL;
|
|
}
|
|
sip_ep->register_state = REGISTER_STATE_UNREGISTERED;
|
|
}
|
|
|
|
static void register_option_timeout(struct timer *timer)
|
|
{
|
|
sip_endpoint_t *sip_ep = timer->priv;
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "register options timer fired\n");
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS (registration)\n");
|
|
|
|
nua_options(sip_ep->register_handle,
|
|
TAG_END());
|
|
}
|
|
|
|
sip_endpoint_t *sip_endpoint_create(int send_no_ringing_after_progress, int receive_no_ringing_after_progress, const char *name, const char *local_user, const char *local_peer, const char *remote_user, const char *remote_peer, const char *asserted_id, int local_register, int remote_register, const char *register_user, const char *register_peer, int authenticate_local, int authenticate_remote, const char *auth_user, const char *auth_password, const char *auth_realm, const char *public_ip, const char *stun_server, int register_interval, int options_interval, int stun_interval, int expires)
|
|
{
|
|
sip_endpoint_t *sip_ep;
|
|
char local[256];
|
|
const char *p;
|
|
|
|
sip_ep = calloc(1, sizeof(*sip_ep));
|
|
if (!sip_ep) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "No mem!\n");
|
|
return NULL;
|
|
}
|
|
|
|
sip_ep->send_no_ringing_after_progress = send_no_ringing_after_progress;
|
|
sip_ep->receive_no_ringing_after_progress = receive_no_ringing_after_progress;
|
|
|
|
sip_ep->name = name;
|
|
|
|
sip_ep->local_user = local_user;
|
|
sip_ep->local_peer = local_peer;
|
|
sip_ep->remote_user = remote_user;
|
|
sip_ep->remote_peer = remote_peer;
|
|
sip_ep->asserted_id = asserted_id;
|
|
|
|
sip_ep->local_register = local_register;
|
|
sip_ep->remote_register = remote_register;
|
|
sip_ep->register_user = register_user;
|
|
sip_ep->register_peer = register_peer;
|
|
|
|
sip_ep->authenticate_local = authenticate_local;
|
|
sip_ep->authenticate_remote = authenticate_remote;
|
|
sip_ep->auth_user = auth_user;
|
|
sip_ep->auth_password = auth_password;
|
|
sip_ep->auth_realm = auth_realm;
|
|
|
|
if (public_ip)
|
|
strncpy(sip_ep->public_ip, public_ip, sizeof(sip_ep->public_ip) - 1);
|
|
sip_ep->stun_server = stun_server;
|
|
|
|
sip_ep->register_interval = register_interval;
|
|
sip_ep->options_interval = options_interval;
|
|
sip_ep->stun_interval = stun_interval;
|
|
|
|
/* create timers */
|
|
timer_init(&sip_ep->stun_retry_timer, stun_retry_timeout, sip_ep);
|
|
timer_init(&sip_ep->register_retry_timer, register_retry_timeout, sip_ep);
|
|
timer_init(&sip_ep->register_option_timer, register_option_timeout, sip_ep);
|
|
|
|
/* init root object */
|
|
sip_ep->su_root = su_root_create(sip_ep);
|
|
if (!sip_ep->su_root) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Failed to create SIP root\n");
|
|
goto error;
|
|
}
|
|
|
|
sprintf(local, "sip:%s",sip_ep->local_peer);
|
|
p = osmo_cc_port_of_address(sip_ep->local_peer);
|
|
if (!p)
|
|
strcat(local, ":5060");
|
|
sip_ep->nua = nua_create(sip_ep->su_root, sip_message, sip_ep, NUTAG_URL(local), TAG_END());
|
|
if (!sip_ep->nua) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Failed to create SIP stack object\n");
|
|
goto error;
|
|
}
|
|
nua_set_params(sip_ep->nua,
|
|
SIPTAG_ALLOW_STR("REGISTER,INVITE,ACK,BYE,CANCEL,OPTIONS,NOTIFY,INFO"),
|
|
NUTAG_APPL_METHOD("REGISTER"),
|
|
NUTAG_APPL_METHOD("INVITE"),
|
|
NUTAG_APPL_METHOD("ACK"),
|
|
/* We want to reply to BYE, so no tag!!! */
|
|
NUTAG_APPL_METHOD("CANCEL"),
|
|
NUTAG_APPL_METHOD("OPTIONS"),
|
|
NUTAG_APPL_METHOD("NOTIFY"),
|
|
NUTAG_APPL_METHOD("INFO"),
|
|
NUTAG_AUTOACK(0),
|
|
#ifdef NUTAG_AUTO100
|
|
NUTAG_AUTO100(0),
|
|
#endif
|
|
NUTAG_AUTOALERT(0),
|
|
NUTAG_AUTOANSWER(0),
|
|
TAG_IF(expires, NUTAG_SESSION_TIMER(expires)),
|
|
// wozu das? NUTAG_ALLOW("INFO"),
|
|
TAG_NULL());
|
|
|
|
if (sip_ep->remote_register)
|
|
sip_ep->register_state = REGISTER_STATE_UNREGISTERED;
|
|
|
|
if (sip_ep->stun_server) {
|
|
sip_ep->stun_handle = stun_handle_init(sip_ep->su_root,
|
|
STUNTAG_SERVER(sip_ep->stun_server),
|
|
TAG_NULL());
|
|
if (!sip_ep->stun_handle) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Failed to create STUN handle\n");
|
|
goto error;
|
|
}
|
|
sip_ep->stun_socket = su_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (sip_ep->stun_socket < 0) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Failed to create STUN socket\n");
|
|
goto error;
|
|
}
|
|
sip_ep->stun_state = STUN_STATE_UNRESOLVED;
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "SIP endpoint created\n");
|
|
|
|
return sip_ep;
|
|
|
|
error:
|
|
sip_endpoint_destroy(sip_ep);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void sip_endpoint_destroy(sip_endpoint_t *sip_ep)
|
|
{
|
|
if (!sip_ep)
|
|
return;
|
|
timer_exit(&sip_ep->stun_retry_timer);
|
|
timer_exit(&sip_ep->register_retry_timer);
|
|
timer_exit(&sip_ep->register_option_timer);
|
|
if (sip_ep->stun_socket)
|
|
su_close(sip_ep->stun_socket);
|
|
if (sip_ep->stun_handle)
|
|
stun_handle_destroy(sip_ep->stun_handle);
|
|
if (sip_ep->register_handle)
|
|
nua_handle_destroy(sip_ep->register_handle);
|
|
if (sip_ep->su_root)
|
|
su_root_destroy(sip_ep->su_root);
|
|
if (sip_ep->nua)
|
|
nua_destroy(sip_ep->nua);
|
|
free(sip_ep);
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "SIP endpoint destroyed\n");
|
|
}
|
|
|
|
extern su_log_t su_log_default[];
|
|
extern su_log_t nua_log[];
|
|
|
|
static su_home_t sip_home[1];
|
|
|
|
/* global init */
|
|
int sip_init(int debug_level)
|
|
{
|
|
/* init SOFIA lib */
|
|
su_init();
|
|
su_home_init(sip_home);
|
|
|
|
if (debug_level) {
|
|
su_log_set_level(su_log_default, debug_level);
|
|
su_log_set_level(nua_log, debug_level);
|
|
//su_log_set_level(soa_log, debug_level);
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "SIP globals initialized\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* global exit */
|
|
void sip_exit(void)
|
|
{
|
|
su_home_deinit(sip_home);
|
|
su_deinit();
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "SIP globals de-initialized\n");
|
|
}
|
|
|
|
/* trigger stun resolver */
|
|
static void sip_handle_stun(sip_endpoint_t *sip_ep)
|
|
{
|
|
int rc;
|
|
|
|
switch (sip_ep->stun_state) {
|
|
case STUN_STATE_UNRESOLVED:
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "STUN resolving for public IP\n");
|
|
|
|
rc = stun_bind(sip_ep->stun_handle, stun_bind_cb, (stun_discovery_magic_t *)sip_ep,
|
|
STUNTAG_SOCKET(sip_ep->stun_socket),
|
|
STUNTAG_REGISTER_EVENTS(1),
|
|
TAG_NULL());
|
|
if (rc < 0) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Failed to call stun_bind()\n");
|
|
sip_ep->stun_state = STUN_STATE_FAILED;
|
|
break;
|
|
}
|
|
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Server = %s\n", sip_ep->stun_server);
|
|
|
|
sip_ep->stun_state = STUN_STATE_RESOLVING;
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
/* trigger registration to remote */
|
|
static void sip_handle_register(sip_endpoint_t *sip_ep)
|
|
{
|
|
char from[256] = "";
|
|
char to[256] = "";
|
|
char contact[256+10] = "";
|
|
char expires[256] = "";
|
|
|
|
switch (sip_ep->register_state) {
|
|
case REGISTER_STATE_UNREGISTERED:
|
|
/* wait for resoved stun */
|
|
if (sip_ep->stun_handle && sip_ep->stun_state != STUN_STATE_RESOLVED)
|
|
return;
|
|
|
|
PDEBUG(DSIP, DEBUG_INFO, "Sending REGISTER\n");
|
|
|
|
sip_ep->register_handle = nua_handle(sip_ep->nua, NULL, TAG_END());
|
|
if (!sip_ep->register_handle) {
|
|
PDEBUG(DSIP, DEBUG_ERROR, "Failed to create handle\n");
|
|
sip_ep->register_state = REGISTER_STATE_FAILED;
|
|
break;
|
|
}
|
|
PDEBUG(DSIP, DEBUG_DEBUG, "new nua_handle %p (register)\n", sip_ep->register_handle);
|
|
|
|
sprintf(from, "sip:%s@%s", sip_ep->register_user, sip_ep->register_peer);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> From = %s\n", from);
|
|
sprintf(to, "sip:%s@%s", sip_ep->register_user, sip_ep->register_peer);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> To = %s\n", to);
|
|
if (sip_ep->public_ip[0]) {
|
|
const char *p;
|
|
sprintf(contact, "sip:%s@%s", sip_ep->register_user, sip_ep->public_ip);
|
|
/* append port of local peer */
|
|
p = osmo_cc_port_of_address(sip_ep->local_peer);
|
|
if (p)
|
|
strcat(contact, p);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Contact = %s\n", contact);
|
|
}
|
|
|
|
if (sip_ep->register_interval) {
|
|
sprintf(expires, "%d", sip_ep->register_interval + 60);
|
|
PDEBUG(DSIP, DEBUG_DEBUG, " -> Expires = %s\n", expires);
|
|
}
|
|
|
|
nua_register(sip_ep->register_handle,
|
|
TAG_IF(from[0], SIPTAG_FROM_STR(from)),
|
|
TAG_IF(to[0], SIPTAG_TO_STR(to)),
|
|
TAG_IF(contact[0], SIPTAG_CONTACT_STR(contact)),
|
|
TAG_IF(expires[0], SIPTAG_EXPIRES_STR(expires)),
|
|
NUTAG_OUTBOUND("no-validate"), // do not validate outbound
|
|
// NUTAG_OUTBOUND("no-options-keepalive"), NUTAG_KEEPALIVE(0), // prevent sending options, but this causes outbound to validate
|
|
TAG_END());
|
|
|
|
sip_ep->register_state = REGISTER_STATE_REGISTERING;
|
|
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
}
|
|
|
|
void sip_handle(sip_endpoint_t *sip_ep)
|
|
{
|
|
/* sofia */
|
|
su_root_step(sip_ep->su_root, 0);
|
|
|
|
/* stun */
|
|
sip_handle_stun(sip_ep);
|
|
|
|
/* register */
|
|
sip_handle_register(sip_ep);
|
|
}
|
|
|