aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKeith <keith@rhizomatica.org>2019-07-30 15:58:55 +0200
committerKeith <keith@rhizomatica.org>2019-08-05 19:05:40 +0200
commit5f73c2033b6b4e690f30292020d7361f48b5f2c2 (patch)
tree5d1da4e044c0acbc49c31581cf29e1b7b55fa296
parentf9a2a414effb27799bc5d64e7690cd376628da65 (diff)
Handle SIP re-INVITEs
SIP end points can send periodic re-INVITES. Previous to this commit, the osmo-sip-connector would send a new call SETUP to the MSC for each re-INVITE. Add a function to find if we already handle this call based on the nua handle. Use this function to detect and respond with an ACK to re-INVITES. Add a function to extract the media mode from the SDP. In the case the re-INVITE has a=sendonly (HOLD) respond with a=recvonly In the case that the re-INVITE changes the media connection ip/port, forward this to the MNCC side with an MNCC_RTP_CONNECT Change-Id: I4083ed50d0cf1b302b80354fe0c2b73fc6e14fed
-rw-r--r--src/call.h3
-rw-r--r--src/mncc.c19
-rw-r--r--src/sdp.c39
-rw-r--r--src/sdp.h1
-rw-r--r--src/sip.c102
5 files changed, 158 insertions, 6 deletions
diff --git a/src/call.h b/src/call.h
index 65d1111..5076c01 100644
--- a/src/call.h
+++ b/src/call.h
@@ -74,6 +74,9 @@ struct call_leg {
* A DTMF key was entered. Forward it.
*/
void (*dtmf)(struct call_leg *, int keypad);
+
+ void (*update_rtp)(struct call_leg *);
+
};
enum sip_cc_state {
diff --git a/src/mncc.c b/src/mncc.c
index ab2bed6..6ee7670 100644
--- a/src/mncc.c
+++ b/src/mncc.c
@@ -198,6 +198,23 @@ static bool send_rtp_connect(struct mncc_call_leg *leg, struct call_leg *other)
return true;
}
+static void update_rtp(struct call_leg *_leg) {
+
+ struct mncc_call_leg *leg;
+
+ LOGP(DMNCC, LOGL_DEBUG, "UPDATE RTP with LEG Type (%u)\n", _leg->type);
+
+ if (_leg->type == CALL_TYPE_MNCC) {
+ leg = (struct mncc_call_leg *) _leg;
+ struct call_leg *other = call_leg_other(&leg->base);
+ send_rtp_connect(leg, other);
+ } else {
+ leg = (struct mncc_call_leg *) call_leg_other(_leg);
+ send_rtp_connect(leg, _leg);
+ }
+}
+
+
/* CONNECT call-back for MNCC call leg */
static void mncc_call_leg_connect(struct call_leg *_leg)
{
@@ -482,6 +499,7 @@ static void check_setup(struct mncc_connection *conn, const char *buf, int rc)
leg->base.connect_call = mncc_call_leg_connect;
leg->base.ring_call = mncc_call_leg_ring;
leg->base.release_call = mncc_call_leg_release;
+ leg->base.update_rtp = update_rtp;
leg->callref = data->callref;
leg->conn = conn;
leg->state = MNCC_CC_INITIAL;
@@ -788,6 +806,7 @@ int mncc_create_remote_leg(struct mncc_connection *conn, struct call *call)
leg->base.ring_call = mncc_call_leg_ring;
leg->base.release_call = mncc_call_leg_release;
leg->base.call = call;
+ leg->base.update_rtp = update_rtp;
leg->callref = call->id;
diff --git a/src/sdp.c b/src/sdp.c
index 9bb55d4..52f7e25 100644
--- a/src/sdp.c
+++ b/src/sdp.c
@@ -33,6 +33,45 @@
#include <string.h>
/*
+ * Check if the media mode attribute exists in SDP, in this
+ * case update the passed pointer with the media mode
+ */
+bool sdp_get_sdp_mode(const sip_t *sip, sdp_mode_t *mode) {
+
+ const char *sdp_data;
+ sdp_parser_t *parser;
+ sdp_session_t *sdp;
+
+ if (!sip->sip_payload || !sip->sip_payload->pl_data) {
+ LOGP(DSIP, LOGL_ERROR, "No SDP file\n");
+ return false;
+ }
+
+ sdp_data = sip->sip_payload->pl_data;
+ parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), sdp_f_mode_0000);
+ if (!parser) {
+ LOGP(DSIP, LOGL_ERROR, "Failed to parse SDP\n");
+ return false;
+ }
+
+ sdp = sdp_session(parser);
+ if (!sdp) {
+ LOGP(DSIP, LOGL_ERROR, "No sdp session\n");
+ sdp_parser_free(parser);
+ return false;
+ }
+
+ if (!sdp->sdp_media || !sdp->sdp_media->m_mode) {
+ sdp_parser_free(parser);
+ return sdp_sendrecv;
+ }
+
+ sdp_parser_free(parser);
+ *mode = sdp->sdp_media->m_mode;
+ return true;
+}
+
+/*
* We want to decide on the audio codec later but we need to see
* if it is even including some of the supported ones.
*/
diff --git a/src/sdp.h b/src/sdp.h
index 72ff6b7..8e4e314 100644
--- a/src/sdp.h
+++ b/src/sdp.h
@@ -8,6 +8,7 @@
struct sip_call_leg;
struct call_leg;
+bool sdp_get_sdp_mode(const sip_t *sip, sdp_mode_t *mode);
bool sdp_screen_sdp(const sip_t *sip);
bool sdp_extract_sdp(struct sip_call_leg *leg, const sip_t *sip, bool any_codec);
diff --git a/src/sip.c b/src/sip.c
index 21401c6..be0d24a 100644
--- a/src/sip.c
+++ b/src/sip.c
@@ -41,6 +41,27 @@ static void sip_ring_call(struct call_leg *_leg);
static void sip_connect_call(struct call_leg *_leg);
static void sip_dtmf_call(struct call_leg *_leg, int keypad);
+/* Find a SIP Call leg by given nua_handle */
+static struct sip_call_leg *sip_find_leg(nua_handle_t *nh)
+{
+ struct call *call;
+
+ llist_for_each_entry(call, &g_call_list, entry) {
+ if (call->initial && call->initial->type == CALL_TYPE_SIP) {
+ struct sip_call_leg *leg = (struct sip_call_leg *) call->initial;
+ if (leg->nua_handle == nh)
+ return leg;
+ }
+ if (call->remote && call->remote->type == CALL_TYPE_SIP) {
+ struct sip_call_leg *leg = (struct sip_call_leg *) call->remote;
+ if (leg->nua_handle == nh)
+ return leg;
+ }
+ }
+
+ return NULL;
+}
+
static void call_progress(struct sip_call_leg *leg, const sip_t *sip, int status)
{
struct call_leg *other = call_leg_other(&leg->base);
@@ -149,6 +170,57 @@ static void new_call(struct sip_agent *agent, nua_handle_t *nh,
talloc_strdup(leg, to));
}
+static void sip_handle_reinvite(struct sip_call_leg *leg, nua_handle_t *nh, const sip_t *sip) {
+
+ char *sdp;
+ sdp_mode_t mode = sdp_sendrecv;
+
+ LOGP(DSIP, LOGL_NOTICE, "re-INVITE for call %s\n", sip->sip_call_id->i_id);
+
+ struct call_leg *other = call_leg_other(&leg->base);
+ if (!sdp_get_sdp_mode(sip, &mode)) {
+ /* re-INVITE with no SDP.
+ * We should respond with SDP reflecting current session
+ */
+ sdp = sdp_create_file(leg, other, sdp_sendrecv);
+ nua_respond(nh, SIP_200_OK,
+ NUTAG_MEDIA_ENABLE(0),
+ SIPTAG_CONTENT_TYPE_STR("application/sdp"),
+ SIPTAG_PAYLOAD_STR(sdp),
+ TAG_END());
+ talloc_free(sdp);
+ return;
+ }
+
+ if (mode == sdp_sendonly) {
+ /* SIP side places call on HOLD */
+ sdp = sdp_create_file(leg, other, sdp_recvonly);
+ /* TODO: Tell core network to stop sending RTP ? */
+ } else {
+ /* SIP re-INVITE may want to change media, IP, port */
+ if (!sdp_extract_sdp(leg, sip, true)) {
+ LOGP(DSIP, LOGL_ERROR, "leg(%p) no audio, releasing\n", leg);
+ nua_respond(nh, SIP_406_NOT_ACCEPTABLE, TAG_END());
+ nua_handle_destroy(nh);
+ call_leg_release(&leg->base);
+ return;
+ }
+ if (other->update_rtp)
+ other->update_rtp(leg->base.call->remote);
+
+ sdp = sdp_create_file(leg, other, sdp_sendrecv);
+ }
+
+ LOGP(DSIP, LOGL_DEBUG, "Sending 200 response to re-INVITE for mode(%u)\n", mode);
+ nua_respond(nh, SIP_200_OK,
+ NUTAG_MEDIA_ENABLE(0),
+ SIPTAG_CONTENT_TYPE_STR("application/sdp"),
+ SIPTAG_PAYLOAD_STR(sdp),
+ TAG_END());
+ talloc_free(sdp);
+ return;
+}
+
/* Sofia SIP definitions come with error code numbers and strings, this
* map allows us to reuse the existing definitions.
* The map is in priority order. The first matching entry found
@@ -235,8 +307,13 @@ void nua_callback(nua_event_t event, int status, char const *phrase, nua_t *nua,
if (status == 180 || status == 183)
call_progress(leg, sip, status);
- else if (status == 200)
- call_connect(leg, sip);
+ else if (status == 200) {
+ struct sip_call_leg *leg = sip_find_leg(nh);
+ if (leg)
+ nua_ack(leg->nua_handle, TAG_END());
+ else
+ call_connect(leg, sip);
+ }
else if (status >= 300) {
struct call_leg *other = call_leg_other(&leg->base);
@@ -251,6 +328,14 @@ void nua_callback(nua_event_t event, int status, char const *phrase, nua_t *nua,
other->release_call(other);
}
}
+ } else if (event == nua_i_ack) {
+ /* SDP comes back to us in 200 ACK after we
+ * respond to the re-INVITE query. */
+ if (sip->sip_payload && sip->sip_payload->pl_data) {
+ struct sip_call_leg *leg = sip_find_leg(nh);
+ if (leg)
+ sip_handle_reinvite(leg, nh, sip);
+ }
} else if (event == nua_r_bye || event == nua_r_cancel) {
/* our bye or hang up is answered */
struct sip_call_leg *leg = (struct sip_call_leg *) hmagic;
@@ -270,10 +355,15 @@ void nua_callback(nua_event_t event, int status, char const *phrase, nua_t *nua,
if (other)
other->release_call(other);
} else if (event == nua_i_invite) {
- /* new incoming leg */
-
- if (status == 100)
- new_call((struct sip_agent *) magic, nh, sip);
+ /* new incoming leg or re-INVITE */
+
+ if (status == 100) {
+ struct sip_call_leg *leg = sip_find_leg(nh);
+ if (leg)
+ sip_handle_reinvite(leg, nh, sip);
+ else
+ new_call((struct sip_agent *) magic, nh, sip);
+ }
} else if (event == nua_i_cancel) {
struct sip_call_leg *leg;
struct call_leg *other;