view sip-out/invite.c @ 167:2ebad02adbe5

themwi-mncc: route outbound calls to themwi-sip-out
author Mychaela Falconia <falcon@freecalypso.org>
date Wed, 12 Oct 2022 18:08:34 -0800
parents bfa9f0c0f0ac
children
line wrap: on
line source

/*
 * In this module we handle responses to INVITE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include "../include/gsm48_const.h"
#include "../include/out_routes.h"
#include "../libsip/parse.h"
#include "../libsip/sdp.h"
#include "../libsip/out_msg.h"
#include "call.h"

extern char *get_single_header();
extern char *extract_to_tag();

extern unsigned sip_linger_invite_err;
extern unsigned sip_linger_response_err;

static
check_sdp_present(msg)
	struct sip_pkt_rx *msg;
{
	char *hval;

	if (!msg->msg_body_len)
		return 0;
	hval = get_single_header(msg, "Content-Type", "c", (int *) 0);
	if (!hval)
		return 0;
	if (!strcasecmp(hval, "application/sdp"))
		return 1;
	else
		return 0;
}

static
extract_sdp(call, msg)
	struct call *call;
	struct sip_pkt_rx *msg;
{
	struct sdp_parse sdp_parse;
	int rc, use_pcma;

	rc = parse_incoming_sdp(msg->msg_body, msg->msg_body_len, &sdp_parse);
	if (rc < 0)
		return rc;
	switch (sdp_parse.codec_mask) {
	case SDP_CODEC_MASK_PCMU:
	case SDP_CODEC_MASK_BOTH:
		use_pcma = 0;
		break;
	case SDP_CODEC_MASK_PCMA:
	case SDP_CODEC_MASK_BOTH | SDP_CODEC_MASK_PCMA_PREF:
		use_pcma = 1;
		break;
	default:
		return -2;
	}
	call->pstn_rtp_remote.sin_family = AF_INET;
	call->pstn_rtp_remote.sin_addr = sdp_parse.ip_addr;
	call->pstn_rtp_remote.sin_port = htons(sdp_parse.audio_port);
	call->use_pcma = use_pcma;
	return 0;
}

static void
handle_1xx(call, msg, tag, sin)
	struct call *call;
	struct sip_pkt_rx *msg;
	char *tag;
	struct sockaddr_in *sin;
{
	int rc;

	switch (call->sip_state) {
	case SIP_STATE_INV_SENT:
		call->sip_state = SIP_STATE_100_RCVD;
		/* FALL THRU */
	case SIP_STATE_100_RCVD:
		if (msg->status_code == 180)
			call->overall_state = OVERALL_STATE_RINGING;
		if (check_sdp_present(msg) &&
		    call->mgw_state == MGW_STATE_ALLOCATED) {
			rc = extract_sdp(call, msg);
			if (rc < 0) {
				syslog(LOG_ERR, "bad SDP in %03u response",
					msg->status_code);
				call->overall_state = OVERALL_STATE_TEARDOWN;
				disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
					GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
				disconnect_tmgw(call);
				initiate_sip_cancel(call);
				return;
			}
			tmgw_send_mdcx_connect(call, 1);
		} else if (msg->status_code == 180)
			mncc_signal_alerting(call);
		return;
	case SIP_STATE_ACCEPT_100:
		initiate_sip_cancel(call);
		return;
	}
}

static
send_ack(call, tag, sin)
	struct call *call;
	char *tag;
	struct sockaddr_in *sin;
{
	struct sip_msg_out msg;
	int rc;

	rc = start_request_out_msg(&msg, "ACK", call->to_uri);
	if (rc < 0)
		return rc;
	rc = add_req_boilerplate(&msg, call, "1 ACK", tag);
	if (rc < 0)
		return rc;
	out_msg_finish(&msg);
	sip_tx_packet(&msg, sin);
	return 0;
}

static void
handle_200(call, msg, tag, sin)
	struct call *call;
	struct sip_pkt_rx *msg;
	char *tag;
	struct sockaddr_in *sin;
{
	int rc;

	switch (call->sip_state) {
	case SIP_STATE_INV_SENT:
	case SIP_STATE_100_RCVD:
		rc = send_ack(call, tag, sin);
		if (rc < 0) {
			syslog(LOG_CRIT,
				"ACK to %03u response exceeds msg size!",
				msg->status_code);
			call->overall_state = OVERALL_STATE_TEARDOWN;
			disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
					GSM48_CC_CAUSE_DEST_OOO);
			disconnect_tmgw(call);
			call->sip_state = SIP_STATE_MSG_SIZE_ERR;
			sip_mark_end_time(call, sip_linger_response_err);
			return;
		}
		if (tag)
			strcpy(call->to_tag, tag);
		call->sip_state = SIP_STATE_CONNECTED;
		if (!check_sdp_present(msg)) {
			syslog(LOG_ERR, "error: %03u response has no SDP",
				msg->status_code);
			call->overall_state = OVERALL_STATE_TEARDOWN;
			disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
					GSM48_CC_CAUSE_DEST_OOO);
			disconnect_tmgw(call);
			initiate_bye(call);
			return;
		}
		rc = extract_sdp(call, msg);
		if (rc < 0) {
			syslog(LOG_ERR, "bad SDP in %03u response",
				msg->status_code);
			call->overall_state = OVERALL_STATE_TEARDOWN;
			disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
			disconnect_tmgw(call);
			initiate_bye(call);
			return;
		}
		call->overall_state = OVERALL_STATE_CONNECTED;
		switch (call->mgw_state) {
		case MGW_STATE_ALLOCATED:
		case MGW_STATE_IBT_CONN:
			tmgw_send_mdcx_connect(call, 0);
			return;
		case MGW_STATE_MDCX_IBT:
			return;
		default:
			syslog(LOG_CRIT,
			"FATAL: invalid MGW state 0x%x on INVITE %03u response",
				call->mgw_state, msg->status_code);
			exit(1);
		}
	case SIP_STATE_CONNECTED:
	case SIP_STATE_BYE_SENT:
		if (tag && call->to_tag[0] && strcmp(call->to_tag, tag)) {
			syslog(LOG_ERR,
			"received %u response with different To tag, ignoring",
				msg->status_code);
			return;
		}
		send_ack(call, call->to_tag, sin);
		return;
	case SIP_STATE_CANCEL_SENT:
	case SIP_STATE_ACCEPT_100:
	case SIP_STATE_ACCEPT_200:
		rc = send_ack(call, tag, sin);
		if (rc < 0) {
			syslog(LOG_CRIT,
				"ACK to %03u response exceeds msg size!",
				msg->status_code);
			call->sip_state = SIP_STATE_MSG_SIZE_ERR;
			sip_mark_end_time(call, sip_linger_response_err);
			return;
		}
		if (tag)
			strcpy(call->to_tag, tag);
		initiate_bye(call);
		return;
	case SIP_STATE_ENDED:
	case SIP_STATE_MSG_SIZE_ERR:
		return;
	default:
		syslog(LOG_CRIT,
			"FATAL: invalid SIP state 0x%x on INVITE %03u response",
			call->sip_state, msg->status_code);
		exit(1);
	}
}

static int
sip_error_to_gsm_cause(sip_status_code)
	unsigned sip_status_code;
{
	/* mapping taken from osmo-sip-connector */
	switch (sip_status_code) {
	case 400:
		return GSM48_CC_CAUSE_TEMP_FAILURE;
	case 401:
	case 402:
	case 403:
		return GSM48_CC_CAUSE_CALL_REJECTED;
	case 404:
		return GSM48_CC_CAUSE_UNASSIGNED_NR;
	case 405:
		return GSM48_CC_CAUSE_SERV_OPT_UNAVAIL;
	case 406:
		return GSM48_CC_CAUSE_CHAN_UNACCEPT;
	case 407:
		return GSM48_CC_CAUSE_CALL_REJECTED;
	case 408:
		return GSM48_CC_CAUSE_RECOVERY_TIMER;
	case 410:
		return GSM48_CC_CAUSE_NUMBER_CHANGED;
	case 413:
	case 414:
		return GSM48_CC_CAUSE_INTERWORKING;
	case 415:
		return GSM48_CC_CAUSE_SERV_OPT_UNIMPL;
	case 416:
		return GSM48_CC_CAUSE_INVAL_TRANS_ID;
	case 420:
	case 421:
	case 423:
		return GSM48_CC_CAUSE_INTERWORKING;
	case 480:
		return GSM48_CC_CAUSE_USER_NOTRESPOND;
	case 481:
		return GSM48_CC_CAUSE_TEMP_FAILURE;
	case 482:
	case 483:
		return GSM48_CC_CAUSE_PRE_EMPTION;
	case 484:
		return GSM48_CC_CAUSE_INV_NR_FORMAT;
	case 485:
		return GSM48_CC_CAUSE_NO_ROUTE;
	case 486:
		return GSM48_CC_CAUSE_USER_BUSY;
	case 488:
		return GSM48_CC_CAUSE_INCOMPAT_DEST;
	case 500:
		return GSM48_CC_CAUSE_TEMP_FAILURE;
	case 501:
		return GSM48_CC_CAUSE_SERV_OPT_UNIMPL;
	case 502:
		return GSM48_CC_CAUSE_DEST_OOO;
	case 503:
		return GSM48_CC_CAUSE_RESOURCE_UNAVAIL;
	case 504:
		return GSM48_CC_CAUSE_RECOVERY_TIMER;
	case 505:
	case 513:
		return GSM48_CC_CAUSE_INTERWORKING;
	case 600:
		return GSM48_CC_CAUSE_USER_BUSY;
	case 603:
		return GSM48_CC_CAUSE_CALL_REJECTED;
	case 604:
		return GSM48_CC_CAUSE_NO_ROUTE;
	case 606:
		return GSM48_CC_CAUSE_INCOMPAT_DEST;
	default:
		return GSM48_CC_CAUSE_NORMAL_UNSPEC;
	}
}

static void
handle_error(call, msg, tag, sin)
	struct call *call;
	struct sip_pkt_rx *msg;
	char *tag;
	struct sockaddr_in *sin;
{
	int rc;

	if (call->sip_state == SIP_STATE_MSG_SIZE_ERR)
		return;
	rc = send_ack(call, tag, sin);
	if (rc < 0)
		syslog(LOG_CRIT, "ACK to %03u response exceeds msg size!",
			msg->status_code);
	switch (call->sip_state) {
	case SIP_STATE_INV_SENT:
	case SIP_STATE_100_RCVD:
		call->overall_state = OVERALL_STATE_TEARDOWN;
		disconnect_mncc(call, GSM48_CAUSE_LOC_NET_BEYOND,
				sip_error_to_gsm_cause(msg->status_code));
		disconnect_tmgw(call);
		call->sip_state = SIP_STATE_ENDED;
		sip_mark_end_time(call, sip_linger_invite_err);
		return;
	case SIP_STATE_CANCEL_SENT:
	case SIP_STATE_ACCEPT_100:
	case SIP_STATE_ACCEPT_200:
		call->sip_state = SIP_STATE_ENDED;
		sip_mark_end_time(call, sip_linger_invite_err);
		return;
	case SIP_STATE_CONNECTED:
	case SIP_STATE_BYE_SENT:
	case SIP_STATE_ENDED:
		return;
	default:
		syslog(LOG_CRIT,
			"FATAL: invalid SIP state 0x%x on INVITE %03u response",
			call->sip_state, msg->status_code);
		exit(1);
	}
}

void
handle_invite_response(call, msg, sin)
	struct call *call;
	struct sip_pkt_rx *msg;
	struct sockaddr_in *sin;
{
	char *tag;

	tag = extract_to_tag(msg, call->to_uri);
	if (tag) {
		if (!*tag) {
			syslog(LOG_ERR,
				"To tag in INVITE %03u response is null",
				msg->status_code);
			tag = 0;
		} else if (strlen(tag) > MAX_SIP_TO_TAG) {
			syslog(LOG_ERR,
			"To tag in INVITE %03u response exceeds length limit",
				msg->status_code);
			tag = 0;
		}
	}
	if (msg->status_code >= 100 && msg->status_code <= 199)
		handle_1xx(call, msg, tag, sin);
	else if (msg->status_code >= 200 && msg->status_code <= 299)
		handle_200(call, msg, tag, sin);
	else
		handle_error(call, msg, tag, sin);
}