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
This commit is contained in:
parent
2e36090fea
commit
2211c3ba56
16
src/call.h
16
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 {
|
||||
|
|
268
src/sip.c
268
src/sip.c
|
@ -20,6 +20,12 @@
|
|||
|
||||
#include "sip.h"
|
||||
#include "app.h"
|
||||
#include "call.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <sofia-sip/sdp.h>
|
||||
|
||||
#include <talloc.h>
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <sofia-sip/nua.h>
|
||||
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue