/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Philipp Maier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include <osmocom/mgcp_client/mgcp_client.h> #include <osmocom/mgcp_client/mgcp_client_internal.h> #include <osmocom/mgcp_client/mgcp_client_fsm.h> #include <osmocom/core/utils.h> #include <osmocom/core/fsm.h> #include <osmocom/core/byteswap.h> #include <arpa/inet.h> #include <osmocom/core/logging.h> #include <osmocom/core/sockaddr_str.h> /* Context information, this is attached to the priv pointer of the FSM and * is also handed back when dispatcheing events to the parent FSM. This is * purly intened and not meant to be accessible for the API user */ struct mgcp_ctx { /* MGCP client instance that is used to interact with the MGW */ struct mgcp_client *mgcp; /* The ID of the last pending transaction. This is used internally * to cancel the transaction in case of an error */ mgcp_trans_id_t mgw_pending_trans; /* Flag to mark that there is a pending transaction */ bool mgw_trans_pending; /* Connection ID which has been assigned by he MGW */ char conn_id[MGCP_CONN_ID_MAXLEN]; /* Local RTP connection info, the MGW will send outgoing traffic to the * ip/port specified here. The Address does not have to be choosen right * on the creation of a connection. It can always be modified later by * the user. */ struct mgcp_conn_peer conn_peer_local; /* Remote RTP connection info, the ip/port specified here is the address * where the MGW expects the RTP data to be sent. This address is * defined by soly by the MGW and can not be influenced by the user. */ struct mgcp_conn_peer conn_peer_remote; /* The terminate flag is a way to handle cornercase sitations that * might occur when the user runs into an error situation and sends * a DLCX command while the FSM is waiting for a response. In this * case the DLCX command is not executed immediately. Instead the * terminate flag is set. When the response to from the previous * operation is received, we know that there is a DLCX event is * pending. The FSM then generates the EV_DLCX by itsself before * it enters ST_READY to cause the immediate execution of the * DLCX procedure. (If normal operations are executed too fast, * the API functions will return an error. In general, the user * should synchronize using the callback events) */ bool terminate; /* Event that is sent when the current operation is completed (except * for DLCX, there the specified parent_term_evt is sent instead) */ uint32_t parent_evt; }; #define S(x) (1 << (x)) #define MGCP_MGW_TIMEOUT 4 /* in seconds */ #define MGCP_MGW_TIMEOUT_TIMER_NR 1 enum fsm_mgcp_client_states { ST_CRCX, ST_CRCX_RESP, ST_READY, ST_MDCX_RESP, ST_DLCX_RESP, }; enum fsm_mgcp_client_evt { EV_CRCX, EV_CRCX_RESP, EV_MDCX, EV_MDCX_RESP, EV_DLCX, EV_DLCX_RESP, }; static const struct value_string fsm_mgcp_client_evt_names[] = { OSMO_VALUE_STRING(EV_CRCX), OSMO_VALUE_STRING(EV_CRCX_RESP), OSMO_VALUE_STRING(EV_MDCX), OSMO_VALUE_STRING(EV_MDCX_RESP), OSMO_VALUE_STRING(EV_DLCX), OSMO_VALUE_STRING(EV_DLCX_RESP), {0, NULL} }; static void make_crcx_msg(struct mgcp_msg *mgcp_msg, struct mgcp_conn_peer *info) { *mgcp_msg = (struct mgcp_msg) { .verb = MGCP_VERB_CRCX, .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE), .call_id = info->call_id, .conn_mode = MGCP_CONN_RECV_ONLY, .ptime = info->ptime, .ptmap_len = info->ptmap_len, .param_present = info->param_present }; osmo_strlcpy(mgcp_msg->endpoint, info->endpoint, MGCP_ENDPOINT_MAXLEN); memcpy(mgcp_msg->ptmap, info->ptmap, sizeof(mgcp_msg->ptmap)); memcpy(&mgcp_msg->param, &info->param, sizeof(mgcp_msg->param)); if (info->x_osmo_ign) { mgcp_msg->x_osmo_ign = info->x_osmo_ign; mgcp_msg->presence |= MGCP_MSG_PRESENCE_X_OSMO_IGN; } if (info->x_osmo_osmux_use) { mgcp_msg->x_osmo_osmux_cid = info->x_osmo_osmux_cid; mgcp_msg->presence |= MGCP_MSG_PRESENCE_X_OSMO_OSMUX_CID; } } static void add_audio(struct mgcp_msg *mgcp_msg, struct mgcp_conn_peer *info) { bool ip_is_set = info->addr[0] != '\0' && strncmp(info->addr, "::", sizeof(info->addr)) != 0 && strncmp(info->addr, "0.0.0.0", sizeof(info->addr)) != 0; if (ip_is_set) { mgcp_msg->presence |= MGCP_MSG_PRESENCE_AUDIO_IP; mgcp_msg->audio_ip = info->addr; } if (info->port) { mgcp_msg->presence |= MGCP_MSG_PRESENCE_AUDIO_PORT; mgcp_msg->audio_port = info->port; } if (ip_is_set && info->port) mgcp_msg->conn_mode = MGCP_CONN_RECV_SEND; } static void set_conn_mode(struct mgcp_msg *mgcp_msg, struct mgcp_conn_peer *peer) { enum mgcp_connection_mode conn_mode = peer->conn_mode; if (conn_mode != MGCP_CONN_NONE) mgcp_msg->conn_mode = conn_mode; } static struct msgb *make_mdcx_msg(struct mgcp_ctx *mgcp_ctx) { struct mgcp_msg mgcp_msg; mgcp_msg = (struct mgcp_msg) { .verb = MGCP_VERB_MDCX, .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT), .call_id = mgcp_ctx->conn_peer_remote.call_id, .conn_id = mgcp_ctx->conn_id, .conn_mode = MGCP_CONN_RECV_SEND, .audio_ip = mgcp_ctx->conn_peer_local.addr, .audio_port = mgcp_ctx->conn_peer_local.port, .ptime = mgcp_ctx->conn_peer_local.ptime, .ptmap_len = mgcp_ctx->conn_peer_local.ptmap_len, .param_present = mgcp_ctx->conn_peer_local.param_present }; osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN); memcpy(mgcp_msg.ptmap, mgcp_ctx->conn_peer_local.ptmap, sizeof(mgcp_msg.ptmap)); memcpy(&mgcp_msg.param, &mgcp_ctx->conn_peer_local.param, sizeof(mgcp_ctx->conn_peer_local.param)); set_conn_mode(&mgcp_msg, &mgcp_ctx->conn_peer_local); if (mgcp_ctx->conn_peer_local.x_osmo_osmux_use) { mgcp_msg.x_osmo_osmux_cid = mgcp_ctx->conn_peer_local.x_osmo_osmux_cid; mgcp_msg.presence |= MGCP_MSG_PRESENCE_X_OSMO_OSMUX_CID; } /* Note: We take the endpoint and the call_id from the remote * connection info, because we can be confident that the * information there is valid. For the local info, we explicitly * allow endpoint and call_id to be optional */ return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg); } struct msgb *make_dlcx_msg(struct mgcp_ctx *mgcp_ctx) { struct mgcp_msg mgcp_msg; mgcp_msg = (struct mgcp_msg) { .verb = MGCP_VERB_DLCX, .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID), .call_id = mgcp_ctx->conn_peer_remote.call_id, .conn_id = mgcp_ctx->conn_id, }; osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN); return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg); } static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv); static void fsm_crcx_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct mgcp_ctx *mgcp_ctx = data; struct mgcp_client *mgcp; struct mgcp_msg mgcp_msg; struct msgb *msg; int rc; OSMO_ASSERT(mgcp_ctx); mgcp = mgcp_ctx->mgcp; OSMO_ASSERT(mgcp); switch (event) { case EV_CRCX: LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: creating connection on MGW endpoint:%s...\n", mgcp_ctx->conn_peer_local.endpoint); make_crcx_msg(&mgcp_msg, &mgcp_ctx->conn_peer_local); add_audio(&mgcp_msg, &mgcp_ctx->conn_peer_local); set_conn_mode(&mgcp_msg, &mgcp_ctx->conn_peer_local); msg = mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg); OSMO_ASSERT(msg); mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg); mgcp_ctx->mgw_trans_pending = true; rc = mgcp_client_tx(mgcp, msg, mgw_crcx_resp_cb, fi); if (rc < 0) { osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } osmo_fsm_inst_state_chg(fi, ST_CRCX_RESP, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); break; default: OSMO_ASSERT(false); break; } } /* Return the CI that the MGW allocated during CRCX response. This is purely informational for logging * and identity tracking; the mgcp_conn_*() functions take care of using the right CI internally. */ const char *mgcp_conn_get_ci(struct osmo_fsm_inst *fi) { struct mgcp_ctx *mgcp_ctx = fi->priv; return mgcp_ctx->conn_id; } /* Get the mgcp_client that is used with this mgcp_client_fsm instance */ struct mgcp_client *mgcp_conn_get_client(struct osmo_fsm_inst *fi) { struct mgcp_ctx *mgcp_ctx; if (!fi) return NULL; mgcp_ctx = fi->priv; return mgcp_ctx->mgcp; } static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv) { struct osmo_fsm_inst *fi = priv; struct mgcp_ctx *mgcp_ctx; int rc; OSMO_ASSERT(fi); mgcp_ctx = fi->priv; OSMO_ASSERT(mgcp_ctx); mgcp_ctx->mgw_trans_pending = false; if (r->head.response_code != 200) { LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: response yields error: %d %s\n", r->head.response_code, r->head.comment); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } osmo_strlcpy(mgcp_ctx->conn_id, r->head.conn_id, sizeof(mgcp_ctx->conn_id)); LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded with CI: %s\n", mgcp_ctx->conn_id); rc = mgcp_response_parse_params(r); if (rc) { LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: Cannot parse CRCX response\n"); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port); if (r->head.x_osmo_osmux_use) { LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded using Osmux %u\n", r->head.x_osmo_osmux_cid); mgcp_ctx->conn_peer_remote.x_osmo_osmux_use = true; mgcp_ctx->conn_peer_remote.x_osmo_osmux_cid = r->head.x_osmo_osmux_cid; } osmo_strlcpy(mgcp_ctx->conn_peer_remote.addr, r->audio_ip, sizeof(mgcp_ctx->conn_peer_remote.addr)); mgcp_ctx->conn_peer_remote.port = r->audio_port; if (strlen(r->head.endpoint) > 0) { /* If we get an endpoint identifier back from the MGW, take it */ osmo_strlcpy(mgcp_ctx->conn_peer_remote.endpoint, r->head.endpoint, sizeof(mgcp_ctx->conn_peer_remote.endpoint)); } else if (strstr(mgcp_ctx->conn_peer_local.endpoint, "*") == NULL) { /* If we do not get an endpoint identifier back and the * identifier we used to create the connection is not a * wildcarded one, we take the local endpoint identifier * instead */ osmo_strlcpy(mgcp_ctx->conn_peer_remote.endpoint, mgcp_ctx->conn_peer_local.endpoint, sizeof(mgcp_ctx->conn_peer_local.endpoint)); } else { LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: CRCX yielded not suitable endpoint identifier\n"); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } mgcp_ctx->conn_peer_remote.call_id = mgcp_ctx->conn_peer_local.call_id; osmo_fsm_inst_dispatch(fi, EV_CRCX_RESP, mgcp_ctx); } static void fsm_crcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct mgcp_ctx *mgcp_ctx = data; OSMO_ASSERT(mgcp_ctx); switch (event) { case EV_CRCX_RESP: osmo_fsm_inst_state_chg(fi, ST_READY, 0, 0); if (mgcp_ctx->terminate) { /* Trigger immediate DLCX if DLCX was requested while the FSM was * busy with the previous operation */ LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: FSM was busy while DLCX was requested, executing now...\n"); osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx); } else osmo_fsm_inst_dispatch(fi->proc.parent, mgcp_ctx->parent_evt, &mgcp_ctx->conn_peer_remote); break; default: OSMO_ASSERT(false); break; } } static void mgw_mdcx_resp_cb(struct mgcp_response *r, void *priv); static void mgw_dlcx_resp_cb(struct mgcp_response *r, void *priv); static void fsm_ready_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct mgcp_ctx *mgcp_ctx = data; struct msgb *msg; struct mgcp_client *mgcp; uint32_t new_state; int rc; OSMO_ASSERT(mgcp_ctx); mgcp = mgcp_ctx->mgcp; OSMO_ASSERT(mgcp); switch (event) { case EV_MDCX: msg = make_mdcx_msg(mgcp_ctx); if (!msg) { /* make_mdcx_msg() should already have logged the error */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } rc = mgcp_client_tx(mgcp, msg, mgw_mdcx_resp_cb, fi); new_state = ST_MDCX_RESP; break; case EV_DLCX: msg = make_dlcx_msg(mgcp_ctx); if (!msg) { /* make_dlcx_msg() should already have logged the error */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } rc = mgcp_client_tx(mgcp, msg, mgw_dlcx_resp_cb, fi); new_state = ST_DLCX_RESP; break; default: OSMO_ASSERT(false); break; } mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg); mgcp_ctx->mgw_trans_pending = true; if (rc < 0) { osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } osmo_fsm_inst_state_chg(fi, new_state, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); } static void mgw_mdcx_resp_cb(struct mgcp_response *r, void *priv) { struct osmo_fsm_inst *fi = priv; struct mgcp_ctx *mgcp_ctx; int rc; OSMO_ASSERT(fi); mgcp_ctx = fi->priv; OSMO_ASSERT(mgcp_ctx); mgcp_ctx->mgw_trans_pending = false; if (r->head.response_code != 200) { LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: response yields error: %d %s\n", r->head.response_code, r->head.comment); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } rc = mgcp_response_parse_params(r); if (rc) { LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: Cannot parse MDCX response\n"); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } LOGPFSML(fi, LOGL_DEBUG, "MGW/MDCX: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port); if (r->head.x_osmo_osmux_use) { LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded using Osmux %u\n", r->head.x_osmo_osmux_cid); mgcp_ctx->conn_peer_remote.x_osmo_osmux_use = true; mgcp_ctx->conn_peer_remote.x_osmo_osmux_cid = r->head.x_osmo_osmux_cid; } osmo_strlcpy(mgcp_ctx->conn_peer_remote.addr, r->audio_ip, sizeof(mgcp_ctx->conn_peer_remote.addr)); mgcp_ctx->conn_peer_remote.port = r->audio_port; osmo_fsm_inst_dispatch(fi, EV_MDCX_RESP, mgcp_ctx); } static void fsm_mdcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct mgcp_ctx *mgcp_ctx = data; OSMO_ASSERT(mgcp_ctx); switch (event) { case EV_MDCX_RESP: osmo_fsm_inst_state_chg(fi, ST_READY, 0, 0); if (mgcp_ctx->terminate) { /* Trigger immediate DLCX if DLCX was requested while the FSM was * busy with the previous operation */ LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: FSM was busy while DLCX was requested, executing now...\n"); osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx); } else osmo_fsm_inst_dispatch(fi->proc.parent, mgcp_ctx->parent_evt, &mgcp_ctx->conn_peer_remote); break; default: OSMO_ASSERT(false); break; } } static void mgw_dlcx_resp_cb(struct mgcp_response *r, void *priv) { struct osmo_fsm_inst *fi = priv; struct mgcp_ctx *mgcp_ctx; OSMO_ASSERT(fi); mgcp_ctx = fi->priv; OSMO_ASSERT(mgcp_ctx); mgcp_ctx->mgw_trans_pending = false; if (r->head.response_code != 250) { LOGPFSML(fi, LOGL_ERROR, "MGW/DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); return; } osmo_fsm_inst_dispatch(fi, EV_DLCX_RESP, mgcp_ctx); } static void fsm_dlcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct mgcp_ctx *mgcp_ctx = data; OSMO_ASSERT(mgcp_ctx); switch (event) { case EV_DLCX_RESP: /* Rub out the connection identifier, since the connection * is no longer present and we will use the connection id * to know in error cases if the connection is still present * or not */ memset(mgcp_ctx->conn_id, 0, sizeof(mgcp_ctx->conn_id)); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); break; default: OSMO_ASSERT(false); break; } } static int fsm_timeout_cb(struct osmo_fsm_inst *fi) { struct mgcp_ctx *mgcp_ctx = fi->priv; struct mgcp_client *mgcp; OSMO_ASSERT(mgcp_ctx); mgcp = mgcp_ctx->mgcp; OSMO_ASSERT(mgcp); if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) { /* Note: We were unable to communicate with the MGW, * unfortunately there is no meaningful action we can take * now other than giving up. */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } else { /* Note: Ther must not be any unsolicited timers * in this FSM. If so, we have serious problem. */ OSMO_ASSERT(false); } return 0; } static void fsm_cleanup_cb(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct mgcp_ctx *mgcp_ctx = fi->priv; struct mgcp_client *mgcp; struct msgb *msg; OSMO_ASSERT(mgcp_ctx); mgcp = mgcp_ctx->mgcp; OSMO_ASSERT(mgcp); /* If there is still a transaction pending, cancel it now. */ if (mgcp_ctx->mgw_trans_pending) mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans); /* Should the FSM be terminated while there are still open connections * on the MGW, we send an unconditional DLCX to terminate the * connection. This is not the normal case. The user should always use * mgcp_conn_delete() to instruct the FSM to perform a graceful exit. * If in ST_DLCX_RESP, a DLCX was already sent and we did not get a * response. No point in sending another one. */ if (fi->state != ST_DLCX_RESP && strlen(mgcp_ctx->conn_id)) { LOGPFSML(fi, LOGL_INFO, "Conn cleanup, sending DLCX for %s %s\n", mgcp_ctx->conn_peer_remote.endpoint, mgcp_ctx->conn_id); msg = make_dlcx_msg(mgcp_ctx); if (!msg) LOGPFSML(fi, LOGL_ERROR, "MGW/DLCX: Error composing DLCX message\n"); else mgcp_client_tx(mgcp, msg, NULL, NULL); } talloc_free(mgcp_ctx); } static struct osmo_fsm_state fsm_mgcp_client_states[] = { /* Initial CRCX state. This state is immediately entered and executed * when the FSM is started. The rationale is that we first have to * create a connectin before we can execute other operations on that * connection. */ [ST_CRCX] = { .in_event_mask = S(EV_CRCX), .out_state_mask = S(ST_CRCX_RESP), .name = OSMO_STRINGIFY(ST_CRCX), .action = fsm_crcx_cb, }, /* Wait for the response to a CRCX operation, check and process the * results, change to ST_READY afterwards. */ [ST_CRCX_RESP] = { .in_event_mask = S(EV_CRCX_RESP), .out_state_mask = S(ST_READY), .name = OSMO_STRINGIFY(ST_CRCX_RESP), .action = fsm_crcx_resp_cb, }, /* In this idle state we wait for further operations (e.g. MDCX) that * can be executed by the user using the API. There is no timeout in * this state. The connection lives on until the user decides to * terminate it (DLCX). */ [ST_READY] = { .in_event_mask = S(EV_MDCX) | S(EV_DLCX), .out_state_mask = S(ST_MDCX_RESP) | S(ST_DLCX_RESP), .name = OSMO_STRINGIFY(ST_READY), .action = fsm_ready_cb, }, /* Wait for the response of a MDCX operation, check and process the * results, change to ST_READY afterwards. */ [ST_MDCX_RESP] = { .in_event_mask = S(EV_MDCX_RESP), .out_state_mask = S(ST_READY), .name = OSMO_STRINGIFY(ST_MDCX_RESP), .action = fsm_mdcx_resp_cb, }, /* Wait for the response of a DLCX operation and terminate the FSM * normally. */ [ST_DLCX_RESP] = { .in_event_mask = S(EV_DLCX_RESP), .out_state_mask = 0, .name = OSMO_STRINGIFY(ST_DLCX_RESP), .action = fsm_dlcx_resp_cb, }, }; static struct osmo_fsm fsm_mgcp_client = { .name = "MGCP_CONN", .states = fsm_mgcp_client_states, .num_states = ARRAY_SIZE(fsm_mgcp_client_states), .timer_cb = fsm_timeout_cb, .cleanup = fsm_cleanup_cb, .event_names = fsm_mgcp_client_evt_names, .log_subsys = DLMGCP, }; /* Provide backwards compat for deprecated conn_peer->codecs[]: when the caller passes in an mgcp_conn_peer instance * that has codecs[] set, apply it to ptmap[] instead. */ static void mgcp_conn_peer_compat(struct mgcp_conn_peer *conn_peer) { struct ptmap ptmap[MGCP_MAX_CODECS]; unsigned int ptmap_len; if (!conn_peer->codecs_len) return; /* Before dropping codecs[], codecs[] would indicate the order in which the codecs should appear in SDP. ptmap[] * would indicate payload type numbers when not using a default payload type number (may omit entries). * Now, ptmap[] just indicates both at the same time; codecs[] should be empty, and ptmap[] lists all codecs. * So if any codecs[] are present, recreate ptmap[] in the order of codecs[]. */ ptmap_len = 0; for (int i = 0; i < conn_peer->codecs_len; i++) { enum mgcp_codecs codec = conn_peer->codecs[i]; struct ptmap *found = NULL; /* Look up whether a specific pt was indicated for this codec */ for (int p = 0; p < conn_peer->ptmap_len; p++) { if (conn_peer->ptmap[p].codec != codec) continue; found = &conn_peer->ptmap[p]; break; } if (found) { ptmap[ptmap_len] = *found; } else { ptmap[ptmap_len] = (struct ptmap){ .codec = codec, /* some enum mgcp_codecs correspond to their standard PT nr, so for compat: */ .pt = codec, }; } ptmap_len++; } /* Are there any entries in the old ptmap that were omitted by codecs[]? */ for (int p = 0; p < conn_peer->ptmap_len; p++) { bool exists = false; for (int i = 0; i < ptmap_len; i++) { if (ptmap_cmp(&ptmap[i], &conn_peer->ptmap[p])) continue; exists = true; break; } if (exists) continue; if (ptmap_len >= ARRAY_SIZE(ptmap)) break; /* Not present yet, add it to the end */ ptmap[ptmap_len] = conn_peer->ptmap[p]; ptmap_len++; } /* Use the new ptmap[], and clear out legacy codecs[]. */ memcpy(conn_peer->ptmap, ptmap, sizeof(conn_peer->ptmap)); conn_peer->ptmap_len = ptmap_len; conn_peer->codecs_len = 0; } /*! allocate FSM, and create a new connection on the MGW. * \param[in] mgcp MGCP client descriptor. * \param[in] parent_fi Parent FSM instance. * \param[in] parent_term_evt Event to be sent to parent when terminating. * \param[in] parent_evt Event to be sent to parent when operation is done. * \param[in] conn_peer Connection parameters (ip, port...). * \returns newly-allocated, initialized and registered FSM instance, NULL on error. */ struct osmo_fsm_inst *mgcp_conn_create(struct mgcp_client *mgcp, struct osmo_fsm_inst *parent_fi, uint32_t parent_term_evt, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer) { struct mgcp_ctx *mgcp_ctx; struct osmo_fsm_inst *fi; struct in6_addr ip_test; mgcp_conn_peer_compat(conn_peer); OSMO_ASSERT(parent_fi); OSMO_ASSERT(mgcp); OSMO_ASSERT(conn_peer); /* Check if IP/Port information in conn info makes sense */ if (conn_peer->port && inet_pton(osmo_ip_str_type(conn_peer->addr), conn_peer->addr, &ip_test) != 1) return NULL; /* Allocate and configure a new fsm instance */ fi = osmo_fsm_inst_alloc_child(&fsm_mgcp_client, parent_fi, parent_term_evt); OSMO_ASSERT(fi); mgcp_ctx = talloc_zero(fi, struct mgcp_ctx); OSMO_ASSERT(mgcp_ctx); mgcp_ctx->mgcp = mgcp; mgcp_ctx->parent_evt = parent_evt; memcpy(&mgcp_ctx->conn_peer_local, conn_peer, sizeof(mgcp_ctx->conn_peer_local)); fi->priv = mgcp_ctx; /* start state machine */ OSMO_ASSERT(fi->state == ST_CRCX); osmo_fsm_inst_dispatch(fi, EV_CRCX, mgcp_ctx); return fi; } /*! modify an existing connection on the MGW. * \param[in] fi FSM instance. * \param[in] parent_evt Event to be sent to parent when operation is done. * \param[in] conn_peer New connection information (ip, port...). * \returns 0 on success, -EINVAL on error. */ int mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer) { OSMO_ASSERT(fi); struct mgcp_ctx *mgcp_ctx = fi->priv; struct in6_addr ip_test; mgcp_conn_peer_compat(conn_peer); OSMO_ASSERT(mgcp_ctx); OSMO_ASSERT(conn_peer); /* The user must not issue an MDCX before the CRCX has completed, * if this happens, it means that the parent FSM has overhead the * parent_evt (mandatory!) and executed the MDCX without even * waiting for the results. Another reason could be that the * parent FSM got messed up */ OSMO_ASSERT(fi->state != ST_CRCX_RESP); /* If the user tries to issue an MDCX while an DLCX operation is * pending, there must be a serious problem with the paren FSM. * Eeither the parent_term_evt (mandatory!) has been overheard, * or the parant FSM got messed so badly that it still assumes * a live connection although it as killed it. */ OSMO_ASSERT(fi->state != ST_DLCX_RESP); /* Check if IP/Port parameters make sense */ if (conn_peer->port == 0) { LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, port == 0\n"); return -EINVAL; } if (inet_pton(osmo_ip_str_type(conn_peer->addr), conn_peer->addr, &ip_test) != 1) { LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, IP address %s\n", conn_peer->addr); return -EINVAL; } /*! The user may supply an endpoint identifier in conn_peer. The * identifier is then checked. This check is optional. Later steps do * not depend on the endpoint identifier supplied here because it is * already implicitly known from the CRCX phase. */ if (strlen(conn_peer->endpoint) && strcmp(conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint)) { LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, endpoint mismatches: requested %s, should be %s\n", conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint); return -EINVAL; } /*! Note: The call-id is implicitly known from the previous CRCX and * will not be checked even when it is set in conn_peer. */ mgcp_ctx->parent_evt = parent_evt; memcpy(&mgcp_ctx->conn_peer_local, conn_peer, sizeof(mgcp_ctx->conn_peer_local)); osmo_fsm_inst_dispatch(fi, EV_MDCX, mgcp_ctx); return 0; } /*! delete existing connection on the MGW, destroy FSM afterwards. * \param[in] fi FSM instance. */ void mgcp_conn_delete(struct osmo_fsm_inst *fi) { OSMO_ASSERT(fi); struct mgcp_ctx *mgcp_ctx = fi->priv; OSMO_ASSERT(mgcp_ctx); if (fi->proc.terminating) return; /* Unlink FSM from parent, set the struct mgcp_client as new talloc ctx. */ osmo_fsm_inst_unlink_parent(fi, mgcp_ctx->mgcp); /* An error situation where the parent FSM must be killed immediately * may lead into a situation where the DLCX can not be executed right * at that moment because the FSM is still busy with another operation. * In those cases we postpone the DLCX so that the FSM and the * connections on the MGW get cleaned up gracefully. */ if (fi->state != ST_READY) { LOGPFSML(fi, LOGL_ERROR, "MGW: operation still pending, DLCX will be postponed.\n"); mgcp_ctx->terminate = true; return; } osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx); } const char *osmo_mgcpc_conn_peer_name(const struct mgcp_conn_peer *info) { /* I'd be fine with a smaller buffer and accept truncation, but gcc possibly refuses to build if * this buffer is too small. */ static char buf[1024]; if (!info) return "NULL"; if (info->endpoint[0] && info->addr[0]) snprintf(buf, sizeof(buf), "%s:%s:%u", info->endpoint, info->addr, info->port); else if (info->endpoint[0]) snprintf(buf, sizeof(buf), "%s", info->endpoint); else if (info->addr[0]) snprintf(buf, sizeof(buf), "%s:%u", info->addr, info->port); else return "empty"; return buf; } static __attribute__((constructor)) void osmo_mgcp_client_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&fsm_mgcp_client) == 0); }