From 2211c3ba567472786274fe00599211cfcfdbdcca Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Sat, 26 Mar 2016 06:17:29 +0100 Subject: sip: Implement MT call out to SIP * Create a new handle * Send the invite * Have some state transitions * Allow to release a call in initial unconfirmed state, confirmed one with cancel and connected with bye * Add simple SDP parsing to find the rtpmap/codec that is used by gsm --- src/call.h | 16 ++++ src/sip.c | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/sip.h | 3 + 3 files changed, 287 insertions(+) diff --git a/src/call.h b/src/call.h index a7f03ee..d91f101 100644 --- a/src/call.h +++ b/src/call.h @@ -10,6 +10,9 @@ struct sip_agent; struct mncc_connection; + +struct nua_handle_s; + struct call_leg; /** @@ -62,10 +65,23 @@ struct call_leg { void (*release_call)(struct call_leg *); }; +enum sip_cc_state { + SIP_CC_INITIAL, + SIP_CC_DLG_CNFD, + SIP_CC_CONNECTED, +}; + struct sip_call_leg { + /* base class */ struct call_leg base; + /* back pointer */ struct sip_agent *agent; + + /* per instance members */ + struct nua_handle_s *nua_handle; + enum sip_cc_state state; + const char *wanted_codec; }; enum mncc_cc_state { diff --git a/src/sip.c b/src/sip.c index 8b02b00..0a53e28 100644 --- a/src/sip.c +++ b/src/sip.c @@ -20,6 +20,12 @@ #include "sip.h" #include "app.h" +#include "call.h" +#include "logging.h" + +#include + +#include #include @@ -27,8 +33,270 @@ extern void *tall_mncc_ctx; +static bool extract_sdp(struct sip_call_leg *leg, const sip_t *sip) +{ + sdp_connection_t *conn; + sdp_session_t *sdp; + sdp_parser_t *parser; + sdp_media_t *media; + const char *sdp_data; + bool found_conn = false, found_map = false; + + if (!sip->sip_payload || !sip->sip_payload->pl_data) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) but no SDP file\n", leg); + return false; + } + + sdp_data = sip->sip_payload->pl_data; + parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), 0); + if (!parser) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to parse SDP\n", + leg); + return false; + } + + sdp = sdp_session(parser); + if (!sdp) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) no sdp session\n", leg); + sdp_parser_free(parser); + return false; + } + + for (conn = sdp->sdp_connection; conn; conn = conn->c_next) { + struct in_addr addr; + + if (conn->c_addrtype != sdp_addr_ip4) + continue; + inet_aton(conn->c_address, &addr); + leg->base.ip = addr.s_addr; + found_conn = true; + break; + } + + for (media = sdp->sdp_media; media; media = media->m_next) { + sdp_rtpmap_t *map; + + if (media->m_proto != sdp_proto_rtp) + continue; + if (media->m_type != sdp_media_audio) + continue; + + for (map = media->m_rtpmaps; map; map = map->rm_next) { + if (strcasecmp(map->rm_encoding, leg->wanted_codec) != 0) + continue; + + leg->base.port = media->m_port; + leg->base.payload_type = map->rm_pt; + found_map = true; + break; + } + + if (found_map) + break; + } + + if (!found_conn || !found_map) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) did not find %d/%d\n", + leg, found_conn, found_map); + sdp_parser_free(parser); + return false; + } + + sdp_parser_free(parser); + return true; +} + +static void call_progress(struct sip_call_leg *leg, const sip_t *sip) +{ + struct call_leg *other = call_leg_other(&leg->base); + + if (!other) + return; + + LOGP(DSIP, LOGL_NOTICE, "leg(%p) is now rining.\n", leg); + other->ring_call(other); +} + +static void call_connect(struct sip_call_leg *leg, const sip_t *sip) +{ + /* extract SDP file and if compatible continue */ + struct call_leg *other = call_leg_other(&leg->base); + + if (!other) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) connected but leg gone\n", leg); + nua_cancel(leg->nua_handle, TAG_END()); + return; + } + + if (!extract_sdp(leg, sip)) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) incompatible audio, releasing\n", leg); + nua_cancel(leg->nua_handle, TAG_END()); + other->release_call(other); + return; + } + + LOGP(DSIP, LOGL_NOTICE, "leg(%p) is now connected.\n", leg); + leg->state = SIP_CC_CONNECTED; + other->connect_call(other); + nua_ack(leg->nua_handle, TAG_END()); +} + void nua_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { + LOGP(DSIP, LOGL_DEBUG, "SIP event(%u) status(%d) phrase(%s) %p\n", + event, status, phrase, hmagic); + + if (event == nua_r_invite) { + struct sip_call_leg *leg; + leg = (struct sip_call_leg *) hmagic; + + /* MT call is moving forward */ + + /* The dialogue is now confirmed */ + if (leg->state == SIP_CC_INITIAL) + leg->state = SIP_CC_DLG_CNFD; + + if (status == 180) + call_progress(leg, sip); + else if (status == 200) + call_connect(leg, sip); + else if (status >= 300) { + struct call_leg *other = call_leg_other(&leg->base); + + LOGP(DSIP, LOGL_ERROR, "leg(%p) unknown err, releasing.\n", leg); + nua_cancel(leg->nua_handle, TAG_END()); + nua_handle_destroy(leg->nua_handle); + call_leg_release(&leg->base); + + if (other) + other->release_call(other); + } + } 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; + LOGP(DSIP, LOGL_NOTICE, "leg(%p) got resp to %s\n", + leg, event == nua_r_bye ? "bye" : "cancel"); + nua_handle_destroy(leg->nua_handle); + call_leg_release(&leg->base); + } else if (event == nua_i_bye) { + /* our remote has hung up */ + struct sip_call_leg *leg = (struct sip_call_leg *) hmagic; + struct call_leg *other = call_leg_other(&leg->base); + + LOGP(DSIP, LOGL_ERROR, "leg(%p) got bye, releasing.\n", leg); + nua_handle_destroy(leg->nua_handle); + call_leg_release(&leg->base); + + if (other) + other->release_call(other); + } +} + + +static void sip_release_call(struct call_leg *_leg) +{ + struct sip_call_leg *leg; + + OSMO_ASSERT(_leg->type == CALL_TYPE_SIP); + leg = (struct sip_call_leg *) _leg; + + /* + * If a dialogue is not confirmed yet, we can probably not do much + * but wait for the timeout. For a confirmed one we can send cancel + * and for a connected one bye. I don't see how sofia-sip is going + * to help us here. + */ + switch (leg->state) { + case SIP_CC_INITIAL: + LOGP(DSIP, LOGL_NOTICE, "Canceling leg(%p) in int state\n", leg); + nua_handle_destroy(leg->nua_handle); + call_leg_release(&leg->base); + break; + case SIP_CC_DLG_CNFD: + LOGP(DSIP, LOGL_NOTICE, "Canceling leg(%p) in cnfd state\n", leg); + nua_cancel(leg->nua_handle, TAG_END()); + break; + case SIP_CC_CONNECTED: + LOGP(DSIP, LOGL_NOTICE, "Ending leg(%p) in con\n", leg); + nua_bye(leg->nua_handle, TAG_END()); + break; + } +} + +static const char *media_name(int ptmsg) +{ + return "GSM"; +} + +static int send_invite(struct sip_agent *agent, struct sip_call_leg *leg, + const char *calling_num, const char *called_num) +{ + struct call_leg *other = leg->base.call->initial; + struct in_addr net = { .s_addr = ntohl(other->ip) }; + + leg->wanted_codec = media_name(other->payload_msg_type); + + char *from = talloc_asprintf(leg, "sip:%s@%s", + calling_num, + agent->app->sip.local_addr); + char *to = talloc_asprintf(leg, "sip:%s@%s", + called_num, + agent->app->sip.remote_addr); + char *sdp = talloc_asprintf(leg, + "v=0\r\n" + "o=Osmocom 0 0 IN IP4 %s\r\n" + "s=GSM Call\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio %d RTP/AVP %d\r\n" + "a=rtpmap:%d %s/8000\r\n", + inet_ntoa(net), inet_ntoa(net), /* never use diff. addr! */ + other->port, other->payload_type, + other->payload_type, + leg->wanted_codec); + + leg->state = SIP_CC_INITIAL; + nua_invite(leg->nua_handle, + SIPTAG_FROM_STR(from), + SIPTAG_TO_STR(to), + NUTAG_MEDIA_ENABLE(0), + SIPTAG_CONTENT_TYPE_STR("application/sdp"), + SIPTAG_PAYLOAD_STR(sdp), + TAG_END()); + + leg->base.call->remote = &leg->base; + talloc_free(from); + talloc_free(to); + talloc_free(sdp); + return 0; +} + +int sip_create_remote_leg(struct sip_agent *agent, struct call *call, + const char *source, const char *dest) +{ + struct sip_call_leg *leg; + + leg = talloc_zero(call, struct sip_call_leg); + if (!leg) { + LOGP(DSIP, LOGL_ERROR, "Failed to allocate leg for call(%u)\n", + call->id); + return -1; + } + + leg->base.type = CALL_TYPE_SIP; + leg->base.call = call; + leg->base.release_call = sip_release_call; + leg->agent = agent; + + leg->nua_handle = nua_handle(agent->nua, leg, TAG_END()); + if (!leg->nua_handle) { + LOGP(DSIP, LOGL_ERROR, "Failed to allocate nua for call(%u)\n", + call->id); + talloc_free(leg); + return -2; + } + + return send_invite(agent, leg, source, dest); } char *make_sip_uri(struct sip_agent *agent) diff --git a/src/sip.h b/src/sip.h index 14ec8dc..4ba0a0e 100644 --- a/src/sip.h +++ b/src/sip.h @@ -8,6 +8,7 @@ #include struct app_config; +struct call; struct sip_agent { struct app_config *app; @@ -19,3 +20,5 @@ struct sip_agent { void sip_agent_init(struct sip_agent *agent, struct app_config *app); int sip_agent_start(struct sip_agent *agent); + +int sip_create_remote_leg(struct sip_agent *agent, struct call *call, const char *local, const char *remote); -- cgit v1.2.3