osmo-msc/src/libmsc/rtp_stream.c

479 lines
15 KiB
C

/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/core/fsm.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/transaction.h>
#include <osmocom/msc/call_leg.h>
#include <osmocom/msc/rtp_stream.h>
#include <osmocom/msc/codec_sdp_cc_t9n.h>
#define LOG_RTPS(rtps, level, fmt, args...) \
LOGPFSML(rtps->fi, level, fmt, ##args)
enum rtp_stream_event {
RTP_STREAM_EV_CRCX_OK,
RTP_STREAM_EV_CRCX_FAIL,
RTP_STREAM_EV_MDCX_OK,
RTP_STREAM_EV_MDCX_FAIL,
};
enum rtp_stream_state {
RTP_STREAM_ST_UNINITIALIZED,
RTP_STREAM_ST_ESTABLISHING,
RTP_STREAM_ST_ESTABLISHED,
RTP_STREAM_ST_DISCARDING,
};
static struct osmo_fsm rtp_stream_fsm;
static struct osmo_tdef_state_timeout rtp_stream_fsm_timeouts[32] = {
[RTP_STREAM_ST_ESTABLISHING] = { .T = -2 },
};
#define rtp_stream_state_chg(rtps, state) \
osmo_tdef_fsm_inst_state_chg((rtps)->fi, state, rtp_stream_fsm_timeouts, g_mgw_tdefs, 5)
static __attribute__((constructor)) void rtp_stream_init()
{
OSMO_ASSERT(osmo_fsm_register(&rtp_stream_fsm) == 0);
}
void rtp_stream_update_id(struct rtp_stream *rtps)
{
char buf[256];
char *p;
struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
OSMO_STRBUF_PRINTF(sb, "%s", rtps->fi->proc.parent->id);
if (rtps->for_trans)
OSMO_STRBUF_PRINTF(sb, ":trans-%u", rtps->for_trans->transaction_id);
OSMO_STRBUF_PRINTF(sb, ":call-%u", rtps->call_id);
OSMO_STRBUF_PRINTF(sb, ":%s", rtp_direction_name(rtps->dir));
if (!osmo_mgcpc_ep_ci_id(rtps->ci)) {
OSMO_STRBUF_PRINTF(sb, ":no-CI");
} else {
OSMO_STRBUF_PRINTF(sb, ":CI-%s", osmo_mgcpc_ep_ci_id(rtps->ci));
if (!osmo_sockaddr_str_is_nonzero(&rtps->remote))
OSMO_STRBUF_PRINTF(sb, ":no-remote-port");
else if (!rtps->remote_sent_to_mgw)
OSMO_STRBUF_PRINTF(sb, ":remote-port-not-sent");
if (!rtps->codecs_known)
OSMO_STRBUF_PRINTF(sb, ":no-codecs");
else if (!rtps->codecs_sent_to_mgw)
OSMO_STRBUF_PRINTF(sb, ":codecs-not-sent");
if (rtps->use_osmux) {
if (rtps->remote_osmux_cid < 0)
OSMO_STRBUF_PRINTF(sb, ":no-remote-osmux-cid");
else if (!rtps->remote_osmux_cid_sent_to_mgw)
OSMO_STRBUF_PRINTF(sb, ":remote-osmux-cid-not-sent");
}
}
if (osmo_sockaddr_str_is_nonzero(&rtps->local))
OSMO_STRBUF_PRINTF(sb, ":local-%s-%u", rtps->local.ip, rtps->local.port);
if (osmo_sockaddr_str_is_nonzero(&rtps->remote))
OSMO_STRBUF_PRINTF(sb, ":remote-%s-%u", rtps->remote.ip, rtps->remote.port);
if (rtps->use_osmux)
OSMO_STRBUF_PRINTF(sb, ":osmux-%d-%d", rtps->local_osmux_cid, rtps->remote_osmux_cid);
/* Replace any dots in the IP address, dots not allowed as FSM instance name */
for (p = buf; *p; p++)
if (*p == '.')
*p = '-';
osmo_fsm_inst_update_id_f(rtps->fi, "%s", buf);
}
/* Allocate RTP stream under a call leg. This is one RTP connection from some remote entity with address and port to a
* local RTP address and port. call_id is stored for sending in MGCP transactions and as logging context. for_trans is
* optional, merely stored for reference by callers, and appears as log context if not NULL. */
struct rtp_stream *rtp_stream_alloc(struct call_leg *parent_call_leg, enum rtp_direction dir,
uint32_t call_id, struct gsm_trans *for_trans)
{
struct osmo_fsm_inst *fi;
struct rtp_stream *rtps;
fi = osmo_fsm_inst_alloc_child(&rtp_stream_fsm, parent_call_leg->fi, CALL_LEG_EV_RTP_STREAM_GONE);
OSMO_ASSERT(fi);
rtps = talloc(fi, struct rtp_stream);
OSMO_ASSERT(rtps);
fi->priv = rtps;
*rtps = (struct rtp_stream){
.fi = fi,
.parent_call_leg = parent_call_leg,
.call_id = call_id,
.for_trans = for_trans,
.dir = dir,
.local_osmux_cid = -2,
.remote_osmux_cid = -2,
};
rtp_stream_update_id(rtps);
return rtps;
}
static void check_established(struct rtp_stream *rtps)
{
if (rtps->fi->state != RTP_STREAM_ST_ESTABLISHED
&& osmo_sockaddr_str_is_nonzero(&rtps->local)
&& osmo_sockaddr_str_is_nonzero(&rtps->remote)
&& rtps->remote_sent_to_mgw
&& (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw)
&& rtps->codecs_known)
rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHED);
}
static void rtp_stream_fsm_establishing_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct rtp_stream *rtps = fi->priv;
const struct mgcp_conn_peer *crcx_info;
switch (event) {
case RTP_STREAM_EV_CRCX_OK:
crcx_info = osmo_mgcpc_ep_ci_get_rtp_info(rtps->ci);
if (!crcx_info) {
LOG_RTPS(rtps, LOGL_ERROR, "osmo_mgcpc_ep_ci_get_rtp_info() has "
"failed, ignoring %s\n", osmo_fsm_event_name(fi->fsm, event));
return;
}
osmo_sockaddr_str_from_str(&rtps->local, crcx_info->addr, crcx_info->port);
if (rtps->use_osmux != crcx_info->x_osmo_osmux_use) {
LOG_RTPS(rtps, LOGL_ERROR, "Osmux usage request and response don't match: %d vs %d",
rtps->use_osmux, crcx_info->x_osmo_osmux_use);
/* TODO: proper failure path */
OSMO_ASSERT(rtps->use_osmux != crcx_info->x_osmo_osmux_use);
}
if (crcx_info->x_osmo_osmux_use)
rtps->local_osmux_cid = crcx_info->x_osmo_osmux_cid;
rtp_stream_update_id(rtps);
osmo_fsm_inst_dispatch(fi->proc.parent, CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE, rtps);
check_established(rtps);
if ((!rtps->remote_sent_to_mgw || !rtps->codecs_sent_to_mgw)
&& osmo_sockaddr_str_is_nonzero(&rtps->remote)
&& (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw)
&& rtps->codecs_known) {
LOG_RTPS(rtps, LOGL_DEBUG,
"local ip:port set;%s%s%s triggering MDCX to send the new settings\n",
(!rtps->remote_sent_to_mgw)? " remote ip:port not yet sent," : "",
(!rtps->codecs_sent_to_mgw)? " codecs not yet sent," : "",
(rtps->use_osmux && !rtps->remote_osmux_cid_sent_to_mgw) ? "Osmux CID not yet sent,": "");
rtp_stream_do_mdcx(rtps);
}
return;
case RTP_STREAM_EV_MDCX_OK:
rtp_stream_update_id(rtps);
check_established(rtps);
return;
case RTP_STREAM_EV_CRCX_FAIL:
case RTP_STREAM_EV_MDCX_FAIL:
rtps->remote_sent_to_mgw = false;
rtps->codecs_sent_to_mgw = false;
rtps->remote_osmux_cid_sent_to_mgw = false;
rtp_stream_update_id(rtps);
rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING);
return;
default:
OSMO_ASSERT(false);
};
}
void rtp_stream_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct rtp_stream *rtps = fi->priv;
osmo_fsm_inst_dispatch(fi->proc.parent, CALL_LEG_EV_RTP_STREAM_ESTABLISHED, rtps);
}
static int rtp_stream_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct rtp_stream *rtps = fi->priv;
rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING);
return 0;
}
static void rtp_stream_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct rtp_stream *rtps = fi->priv;
if (rtps->ci) {
osmo_mgcpc_ep_cancel_notify(osmo_mgcpc_ep_ci_ep(rtps->ci), fi);
osmo_mgcpc_ep_ci_dlcx(rtps->ci);
rtps->ci = NULL;
}
}
void rtp_stream_fsm_discarding_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
static const struct value_string rtp_stream_fsm_event_names[] = {
OSMO_VALUE_STRING(RTP_STREAM_EV_CRCX_OK),
OSMO_VALUE_STRING(RTP_STREAM_EV_CRCX_FAIL),
OSMO_VALUE_STRING(RTP_STREAM_EV_MDCX_OK),
OSMO_VALUE_STRING(RTP_STREAM_EV_MDCX_FAIL),
{}
};
#define S(x) (1 << (x))
static const struct osmo_fsm_state rtp_stream_fsm_states[] = {
[RTP_STREAM_ST_UNINITIALIZED] = {
.name = "UNINITIALIZED",
.out_state_mask = 0
| S(RTP_STREAM_ST_ESTABLISHING)
| S(RTP_STREAM_ST_DISCARDING)
,
},
[RTP_STREAM_ST_ESTABLISHING] = {
.name = "ESTABLISHING",
.in_event_mask = 0
| S(RTP_STREAM_EV_CRCX_OK)
| S(RTP_STREAM_EV_CRCX_FAIL)
| S(RTP_STREAM_EV_MDCX_OK)
| S(RTP_STREAM_EV_MDCX_FAIL)
,
.out_state_mask = 0
| S(RTP_STREAM_ST_ESTABLISHED)
| S(RTP_STREAM_ST_DISCARDING)
,
.action = rtp_stream_fsm_establishing_established,
},
[RTP_STREAM_ST_ESTABLISHED] = {
.name = "ESTABLISHED",
.out_state_mask = 0
| S(RTP_STREAM_ST_ESTABLISHING)
| S(RTP_STREAM_ST_DISCARDING)
,
.onenter = rtp_stream_fsm_established_onenter,
.action = rtp_stream_fsm_establishing_established,
},
[RTP_STREAM_ST_DISCARDING] = {
.name = "DISCARDING",
.onenter = rtp_stream_fsm_discarding_onenter,
.out_state_mask = 0
| S(RTP_STREAM_ST_DISCARDING)
,
},
};
static struct osmo_fsm rtp_stream_fsm = {
.name = "rtp_stream",
.states = rtp_stream_fsm_states,
.num_states = ARRAY_SIZE(rtp_stream_fsm_states),
.log_subsys = DCC,
.event_names = rtp_stream_fsm_event_names,
.timer_cb = rtp_stream_fsm_timer_cb,
.cleanup = rtp_stream_fsm_cleanup,
};
static int rtp_stream_do_mgcp_verb(struct rtp_stream *rtps, enum mgcp_verb verb, uint32_t ok_event, uint32_t fail_event)
{
struct mgcp_conn_peer verb_info;
if (!rtps->ci) {
LOG_RTPS(rtps, LOGL_ERROR, "Cannot send %s, no endpoint CI allocated\n", osmo_mgcp_verb_name(verb));
return -EINVAL;
}
verb_info = (struct mgcp_conn_peer){
.call_id = rtps->call_id,
.ptime = 20,
.x_osmo_osmux_use = rtps->use_osmux,
.x_osmo_osmux_cid = rtps->remote_osmux_cid,
};
if (verb == MGCP_VERB_CRCX)
verb_info.conn_mode = rtps->crcx_conn_mode;
if (rtps->codecs_known) {
/* Send the list of codecs to the MGW. Ideally we would just feed the SDP directly, but for legacy
* reasons we still need to translate to a struct mgcp_conn_peer representation to send it. */
struct sdp_audio_codec *codec;
int i = 0;
foreach_sdp_audio_codec(codec, &rtps->codecs) {
const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name);
if (!m)
continue;
verb_info.codecs[i] = m->mgcp;
verb_info.ptmap[i] = (struct ptmap){
.codec = m->mgcp,
.pt = codec->payload_type,
};
i++;
verb_info.codecs_len = i;
verb_info.ptmap_len = i;
}
rtps->codecs_sent_to_mgw = true;
}
if (osmo_sockaddr_str_is_nonzero(&rtps->remote)) {
int rc = osmo_strlcpy(verb_info.addr, rtps->remote.ip, sizeof(verb_info.addr));
if (rc <= 0 || rc >= sizeof(verb_info.addr)) {
LOG_RTPS(rtps, LOGL_ERROR, "Failure to write IP address to MGCP message (rc=%d)\n", rc);
return -ENOSPC;
}
verb_info.port = rtps->remote.port;
rtps->remote_sent_to_mgw = true;
}
osmo_mgcpc_ep_ci_request(rtps->ci, verb, &verb_info, rtps->fi, ok_event, fail_event, NULL);
return 0;
}
int rtp_stream_ensure_ci(struct rtp_stream *rtps, struct osmo_mgcpc_ep *at_endpoint)
{
if (rtps->ci)
return rtp_stream_commit(rtps);
rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
rtps->ci = osmo_mgcpc_ep_ci_add(at_endpoint, "%s", rtp_direction_name(rtps->dir));
if (!rtps->ci)
return -ENODEV;
return rtp_stream_do_mgcp_verb(rtps, MGCP_VERB_CRCX, RTP_STREAM_EV_CRCX_OK, RTP_STREAM_EV_CRCX_FAIL);
}
int rtp_stream_do_mdcx(struct rtp_stream *rtps)
{
return rtp_stream_do_mgcp_verb(rtps, MGCP_VERB_MDCX, RTP_STREAM_EV_MDCX_OK, RTP_STREAM_EV_MDCX_FAIL);
}
void rtp_stream_release(struct rtp_stream *rtps)
{
if (!rtps)
return;
rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING);
}
/* After setting up a remote RTP address or a new codec, call this to trigger an MDCX.
* The MDCX will only trigger if all data needed by an endpoint is available (both RTP address and codec) and if at
* least one of them has not yet been sent to the MGW in a previous CRCX or MDCX. */
int rtp_stream_commit(struct rtp_stream *rtps)
{
if (!osmo_sockaddr_str_is_nonzero(&rtps->remote)) {
LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no remote RTP address known\n");
return -1;
}
if (!rtps->codecs_known) {
LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no codecs known\n");
return -1;
}
if (rtps->remote_sent_to_mgw && rtps->codecs_sent_to_mgw) {
LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: both remote RTP address and codecs already set up at MGW\n");
return 0;
}
if (!rtps->ci) {
LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no MGW endpoint CI set up\n");
return -1;
}
LOG_RTPS(rtps, LOGL_DEBUG, "Committing: Tx MDCX to update the MGW: updating%s%s%s\n",
rtps->remote_sent_to_mgw ? "" : " remote-RTP-IP-port",
rtps->codecs_sent_to_mgw ? "" : " codecs",
(!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw) ? "" : " remote-Osmux-CID");
return rtp_stream_do_mdcx(rtps);
}
void rtp_stream_set_codecs(struct rtp_stream *rtps, const struct sdp_audio_codecs *codecs)
{
if (!codecs || !codecs->count)
return;
if (sdp_audio_codecs_cmp(&rtps->codecs, codecs, false, true) == 0) {
LOG_RTPS(rtps, LOGL_DEBUG, "no change: codecs already set to %s\n",
sdp_audio_codecs_to_str(&rtps->codecs));
return;
}
if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED)
rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
LOG_RTPS(rtps, LOGL_DEBUG, "setting codecs to %s\n", sdp_audio_codecs_to_str(codecs));
rtps->codecs = *codecs;
rtps->codecs_known = true;
rtps->codecs_sent_to_mgw = false;
rtp_stream_update_id(rtps);
}
/* Convenience shortcut to call rtp_stream_set_codecs() with a list of only one sdp_audio_codec record. */
void rtp_stream_set_one_codec(struct rtp_stream *rtps, const struct sdp_audio_codec *codec)
{
struct sdp_audio_codecs codecs = {};
sdp_audio_codecs_add_copy(&codecs, codec);
rtp_stream_set_codecs(rtps, &codecs);
}
/* For legacy, rather use rtp_stream_set_codecs() with a full codecs list. */
bool rtp_stream_set_codecs_from_mgcp_codec(struct rtp_stream *rtps, enum mgcp_codecs codec)
{
struct sdp_audio_codecs codecs = {};
if (!sdp_audio_codecs_add_mgcp_codec(&codecs, codec))
return false;
rtp_stream_set_codecs(rtps, &codecs);
return true;
}
void rtp_stream_set_remote_addr(struct rtp_stream *rtps, const struct osmo_sockaddr_str *r)
{
if (osmo_sockaddr_str_cmp(&rtps->remote, r) == 0) {
LOG_RTPS(rtps, LOGL_DEBUG, "remote addr already " OSMO_SOCKADDR_STR_FMT ", no change\n",
OSMO_SOCKADDR_STR_FMT_ARGS(r));
return;
}
if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED)
rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
LOG_RTPS(rtps, LOGL_DEBUG, "setting remote addr to " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(r));
rtps->remote = *r;
rtps->remote_sent_to_mgw = false;
rtp_stream_update_id(rtps);
}
void rtp_stream_set_remote_osmux_cid(struct rtp_stream *rtps, uint8_t osmux_cid)
{
if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED)
rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
LOG_RTPS(rtps, LOGL_DEBUG, "setting remote Osmux CID to %u\n", osmux_cid);
rtps->remote_osmux_cid = osmux_cid;
rtps->remote_osmux_cid_sent_to_mgw = false;
rtp_stream_update_id(rtps);
}
bool rtp_stream_is_established(struct rtp_stream *rtps)
{
if (!rtps)
return false;
if (!rtps->fi)
return false;
if (rtps->fi->state != RTP_STREAM_ST_ESTABLISHED)
return false;
if (!rtps->remote_sent_to_mgw
|| !rtps->codecs_sent_to_mgw
|| (rtps->use_osmux && !rtps->remote_osmux_cid_sent_to_mgw))
return false;
return true;
}