From 5f73c2033b6b4e690f30292020d7361f48b5f2c2 Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 30 Jul 2019 15:58:55 +0200 Subject: [PATCH] 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 --- src/call.h | 3 ++ src/mncc.c | 19 ++++++++++ src/sdp.c | 39 +++++++++++++++++++++ src/sdp.h | 1 + src/sip.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 157 insertions(+), 5 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 @@ -32,6 +32,45 @@ #include +/* + * 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 */ + /* new incoming leg or re-INVITE */ - if (status == 100) - new_call((struct sip_agent *) magic, nh, sip); + 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;