| /* |
| * (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_mapping.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) { |
| LOG_RTPS(rtps, LOGL_ERROR, "Cannot map codec '%s' to MGCP: codec is unknown\n", |
| codec->subtype_name); |
| 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; |
| } |
| if (rtps->use_osmux && rtps->remote_osmux_cid >= 0) |
| rtps->remote_osmux_cid_sent_to_mgw = true; |
| rtp_stream_update_id(rtps); |
| |
| 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_addr_and_codecs(struct rtp_stream *rtps, const struct sdp_msg *sdp) |
| { |
| rtp_stream_set_codecs(rtps, &sdp->audio_codecs); |
| if (osmo_sockaddr_str_is_nonzero(&sdp->rtp)) |
| rtp_stream_set_remote_addr(rtps, &sdp->rtp); |
| } |
| |
| 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; |
| } |