blob: 59f2f636d7f57fbd3b3667830eb831ae7738a25c [file] [log] [blame]
Neels Hofmeyrc4628a32018-12-07 14:47:34 +01001/* Implementation to manage two RTP streams that make up an MO or MT call leg's RTP forwarding. */
2/*
Vadim Yanitskiy999a5932023-05-18 17:22:26 +07003 * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
Neels Hofmeyrc4628a32018-12-07 14:47:34 +01004 * All Rights Reserved
5 *
6 * Author: Neels Hofmeyr
7 *
Harald Welte1b1a39b2024-02-17 10:11:18 +01008 * SPDX-License-Identifier: AGPL-3.0+
Neels Hofmeyrc4628a32018-12-07 14:47:34 +01009 *
10 * This program is free software; you can redistribute it and/or modify
Harald Welte1b1a39b2024-02-17 10:11:18 +010011 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
Neels Hofmeyrc4628a32018-12-07 14:47:34 +010013 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte1b1a39b2024-02-17 10:11:18 +010018 * GNU Affero General Public License for more details.
Neels Hofmeyrc4628a32018-12-07 14:47:34 +010019 */
20#include <osmocom/core/fsm.h>
21#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
22
23#include <osmocom/msc/debug.h>
24#include <osmocom/msc/gsm_data.h>
25#include <osmocom/msc/msc_a.h>
26
27#include <osmocom/msc/call_leg.h>
28#include <osmocom/msc/rtp_stream.h>
29
30#define LOG_CALL_LEG(cl, level, fmt, args...) \
31 LOGPFSML(cl ? cl->fi : NULL, level, fmt, ##args)
32
33static struct gsm_network *gsmnet = NULL;
34
35enum call_leg_state {
36 CALL_LEG_ST_ESTABLISHING,
37 CALL_LEG_ST_ESTABLISHED,
38 CALL_LEG_ST_RELEASING,
39};
40
41struct osmo_tdef g_mgw_tdefs[] = {
Neels Hofmeyrd4099c32020-09-16 01:59:29 +020042 { .T=-2427, .default_val=4, .desc="MGCP response timeout" },
Neels Hofmeyrc4628a32018-12-07 14:47:34 +010043 { .T=-2, .default_val=30, .desc="RTP stream establishing timeout" },
44 {}
45};
46
47static const struct osmo_tdef_state_timeout call_leg_fsm_timeouts[32] = {
48 [CALL_LEG_ST_ESTABLISHING] = { .T = -2 },
49 [CALL_LEG_ST_RELEASING] = { .T = -2 },
50};
51
52#define call_leg_state_chg(cl, state) \
53 osmo_tdef_fsm_inst_state_chg((cl)->fi, state, call_leg_fsm_timeouts, g_mgw_tdefs, 10)
54
55static struct osmo_fsm call_leg_fsm;
56
57void call_leg_init(struct gsm_network *net)
58{
59 gsmnet = net;
60 OSMO_ASSERT( osmo_fsm_register(&call_leg_fsm) == 0 );
61}
62
Neels Hofmeyrf50d1302019-05-09 16:23:11 +020063/* Allocate a call leg FSM instance as child of an arbitrary other FSM instance.
64 * The call leg FSM dispatches events to its parent FSM instance on specific events:
65 * - parent_event_term: dispatch this to the parent FI when the call leg terminates (call ended, either planned or by
66 * failure).
67 * - parent_event_rtp_addr_available: one of the rtp_stream instances managed by the call leg has received an RTP
68 * address from the MGW. The struct rtp_stream instance is passed as data argument for the event dispatch.
69 * - parent_event_rtp_complete: one of the rtp_stream instances entered the RTP_STREAM_ST_ESTABLISHED state.
70 */
Neels Hofmeyrc4628a32018-12-07 14:47:34 +010071struct call_leg *call_leg_alloc(struct osmo_fsm_inst *parent_fi,
72 uint32_t parent_event_term,
73 uint32_t parent_event_rtp_addr_available,
Neels Hofmeyr265a4c72019-05-09 16:20:51 +020074 uint32_t parent_event_rtp_complete)
Neels Hofmeyrc4628a32018-12-07 14:47:34 +010075{
76 struct call_leg *cl;
77 struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&call_leg_fsm, parent_fi, parent_event_term);
78
79 OSMO_ASSERT(fi);
80
81 cl = talloc(fi, struct call_leg);
82 OSMO_ASSERT(cl);
83 fi->priv = cl;
84 *cl = (struct call_leg){
85 .fi = fi,
86 .parent_event_rtp_addr_available = parent_event_rtp_addr_available,
87 .parent_event_rtp_complete = parent_event_rtp_complete,
Neels Hofmeyrc4628a32018-12-07 14:47:34 +010088 };
89
90 return cl;
91}
92
93void call_leg_reparent(struct call_leg *cl,
94 struct osmo_fsm_inst *new_parent_fi,
95 uint32_t parent_event_term,
96 uint32_t parent_event_rtp_addr_available,
Neels Hofmeyr265a4c72019-05-09 16:20:51 +020097 uint32_t parent_event_rtp_complete)
Neels Hofmeyrc4628a32018-12-07 14:47:34 +010098{
99 LOG_CALL_LEG(cl, LOGL_DEBUG, "Reparenting from parent %s to parent %s\n",
100 cl->fi->proc.parent->name, new_parent_fi->name);
101 osmo_fsm_inst_change_parent(cl->fi, new_parent_fi, parent_event_term);
102 talloc_steal(new_parent_fi, cl->fi);
103 cl->parent_event_rtp_addr_available = parent_event_rtp_addr_available;
104 cl->parent_event_rtp_complete = parent_event_rtp_complete;
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100105}
106
107static int call_leg_fsm_timer_cb(struct osmo_fsm_inst *fi)
108{
109 struct call_leg *cl = fi->priv;
110 call_leg_release(cl);
111 return 0;
112}
113
114void call_leg_release(struct call_leg *cl)
115{
116 if (!cl)
117 return;
118 if (cl->fi->state == CALL_LEG_ST_RELEASING)
119 return;
120 call_leg_state_chg(cl, CALL_LEG_ST_RELEASING);
121}
122
123static void call_leg_mgw_endpoint_gone(struct call_leg *cl)
124{
Pau Espin Pedrolb44cf2d2022-10-17 18:09:15 +0200125 struct mgcp_client *mgcp_client;
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100126 int i;
Pau Espin Pedrolb44cf2d2022-10-17 18:09:15 +0200127
128 /* Put MGCP client back into MGW pool */
129 mgcp_client = osmo_mgcpc_ep_client(cl->mgw_endpoint);
130 mgcp_client_pool_put(mgcp_client);
131
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100132 cl->mgw_endpoint = NULL;
133 for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
134 if (!cl->rtp[i])
135 continue;
136 cl->rtp[i]->ci = NULL;
137 }
138}
139
140static void call_leg_fsm_establishing_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
141{
142 struct call_leg *cl = fi->priv;
143 struct rtp_stream *rtps;
144 int i;
145 bool established;
146
147 switch (event) {
148
149 case CALL_LEG_EV_RTP_STREAM_ESTABLISHED:
150 /* An rtp_stream says it is established. If all are now established, change to state
151 * CALL_LEG_ST_ESTABLISHED. */
152 established = true;
153 for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
154 if (!rtp_stream_is_established(cl->rtp[i])) {
155 established = false;
156 break;
157 }
158 }
159 if (!established)
160 break;
Neels Hofmeyrd7d3ece2023-11-23 00:05:32 +0100161 if (cl->fi->state != CALL_LEG_ST_ESTABLISHED)
162 call_leg_state_chg(cl, CALL_LEG_ST_ESTABLISHED);
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100163 break;
164
165 case CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE:
166 rtps = data;
167 osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_addr_available, rtps);
168 break;
169
170 case CALL_LEG_EV_RTP_STREAM_GONE:
171 call_leg_release(cl);
172 break;
173
174 case CALL_LEG_EV_MGW_ENDPOINT_GONE:
175 call_leg_mgw_endpoint_gone(cl);
176 call_leg_release(cl);
177 break;
178
179 default:
180 OSMO_ASSERT(false);
181 }
182}
183
184void call_leg_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
185{
186 struct call_leg *cl = fi->priv;
187 osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_complete, cl);
188}
189
190void call_leg_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
191{
Pau Espin Pedrol093fd2e2022-10-19 16:56:43 +0200192 /* Trigger termination of children FSMs (rtp_stream(s)) before
193 * terminating ourselves, otherwise we are not able to receive
194 * CALL_LEG_EV_MGW_ENDPOINT_GONE from cl->mgw_endpoint (call_leg =>
195 * rtp_stream => mgw_endpoint), because osmo_fsm disabled dispatching
196 * events to an FSM in process of terminating. */
197 osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL);
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100198 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
199}
200
201static void call_leg_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
202{
203 struct call_leg *cl = fi->priv;
204
205 switch (event) {
206
207 case CALL_LEG_EV_RTP_STREAM_GONE:
208 /* We're already terminating, child RTP streams will also terminate, there is nothing left to do. */
209 break;
210
211 case CALL_LEG_EV_MGW_ENDPOINT_GONE:
212 call_leg_mgw_endpoint_gone(cl);
213 break;
214
215 default:
216 OSMO_ASSERT(false);
217 }
218}
219
220static const struct value_string call_leg_fsm_event_names[] = {
221 OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE),
222 OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ESTABLISHED),
223 OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_GONE),
224 OSMO_VALUE_STRING(CALL_LEG_EV_MGW_ENDPOINT_GONE),
225 {}
226};
227
228#define S(x) (1 << (x))
229
230static const struct osmo_fsm_state call_leg_fsm_states[] = {
231 [CALL_LEG_ST_ESTABLISHING] = {
232 .name = "ESTABLISHING",
233 .in_event_mask = 0
234 | S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
235 | S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
236 | S(CALL_LEG_EV_RTP_STREAM_GONE)
237 | S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
238 ,
239 .out_state_mask = 0
240 | S(CALL_LEG_ST_ESTABLISHED)
241 | S(CALL_LEG_ST_RELEASING)
242 ,
243 .action = call_leg_fsm_establishing_established,
244 },
245 [CALL_LEG_ST_ESTABLISHED] = {
246 .name = "ESTABLISHED",
247 .in_event_mask = 0
248 | S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
249 | S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
250 | S(CALL_LEG_EV_RTP_STREAM_GONE)
251 | S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
252 ,
253 .out_state_mask = 0
254 | S(CALL_LEG_ST_ESTABLISHING)
255 | S(CALL_LEG_ST_RELEASING)
256 ,
257 .onenter = call_leg_fsm_established_onenter,
258 .action = call_leg_fsm_establishing_established, /* same action function as above */
259 },
260 [CALL_LEG_ST_RELEASING] = {
261 .name = "RELEASING",
262 .in_event_mask = 0
263 | S(CALL_LEG_EV_RTP_STREAM_GONE)
264 | S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
265 ,
266 .onenter = call_leg_fsm_releasing_onenter,
267 .action = call_leg_fsm_releasing,
268 },
269};
270
271static struct osmo_fsm call_leg_fsm = {
272 .name = "call_leg",
273 .states = call_leg_fsm_states,
274 .num_states = ARRAY_SIZE(call_leg_fsm_states),
275 .log_subsys = DCC,
276 .event_names = call_leg_fsm_event_names,
277 .timer_cb = call_leg_fsm_timer_cb,
278};
279
280const struct value_string rtp_direction_names[] = {
281 OSMO_VALUE_STRING(RTP_TO_RAN),
282 OSMO_VALUE_STRING(RTP_TO_CN),
283 {}
284};
285
286int call_leg_ensure_rtp_alloc(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans)
287{
288 if (cl->rtp[dir])
289 return 0;
290
Pau Espin Pedrolb44cf2d2022-10-17 18:09:15 +0200291 if (!cl->mgw_endpoint) {
292 struct mgcp_client *mgcp_client = mgcp_client_pool_get(gsmnet->mgw.mgw_pool);
293 if (!mgcp_client) {
294 LOG_CALL_LEG(cl, LOGL_ERROR,
295 "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n");
296 return -ENODEV;
297 }
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100298 cl->mgw_endpoint = osmo_mgcpc_ep_alloc(cl->fi, CALL_LEG_EV_MGW_ENDPOINT_GONE,
Pau Espin Pedrolb44cf2d2022-10-17 18:09:15 +0200299 mgcp_client, gsmnet->mgw.tdefs, cl->fi->id,
300 "%s", mgcp_client_rtpbridge_wildcard(mgcp_client));
301 }
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100302 if (!cl->mgw_endpoint) {
303 LOG_CALL_LEG(cl, LOGL_ERROR, "failed to setup MGW endpoint\n");
304 return -EIO;
305 }
306
Andreas Eversbergbcb4d6b2023-07-05 15:03:19 +0200307 cl->rtp[dir] = rtp_stream_alloc(cl->fi, CALL_LEG_EV_RTP_STREAM_GONE, CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE,
308 CALL_LEG_EV_RTP_STREAM_ESTABLISHED, dir, call_id, for_trans);
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100309 OSMO_ASSERT(cl->rtp[dir]);
310 return 0;
311}
312
313struct osmo_sockaddr_str *call_leg_local_ip(struct call_leg *cl, enum rtp_direction dir)
314{
315 struct rtp_stream *rtps;
316 if (!cl)
317 return NULL;
318 rtps = cl->rtp[dir];
319 if (!rtps)
320 return NULL;
Neels Hofmeyr84ce2062019-10-05 05:15:25 +0200321 if (!osmo_sockaddr_str_is_nonzero(&rtps->local))
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100322 return NULL;
323 return &rtps->local;
324}
325
326/* Make sure an MGW endpoint CI is set up for an RTP connection.
327 * This is the one-stop for all to either completely set up a new endpoint connection, or to modify an existing one.
328 * If not yet present, allocate the rtp_stream for the given direction.
Neels Hofmeyr62bfa372022-10-31 18:51:07 +0100329 * Then, call rtp_stream_set_codecs() if codecs_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100330 * remote_addr_if_known is non-NULL.
331 * Finally make sure that a CRCX is sent out for this direction, if this has not already happened.
332 * If the CRCX has already happened but new codec / remote_addr data was passed, call rtp_stream_commit() to trigger an
333 * MDCX.
334 */
335int call_leg_ensure_ci(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans,
Neels Hofmeyr62bfa372022-10-31 18:51:07 +0100336 const struct sdp_audio_codecs *codecs_if_known,
337 const struct osmo_sockaddr_str *remote_addr_if_known)
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100338{
339 if (call_leg_ensure_rtp_alloc(cl, dir, call_id, for_trans))
340 return -EIO;
Andreas Eversberg58fe2e02023-06-21 12:37:18 +0200341 rtp_stream_set_mode(cl->rtp[dir], cl->crcx_conn_mode[dir]);
Pau Espin Pedrola3cdab42019-05-09 17:54:08 +0200342 if (dir == RTP_TO_RAN && cl->ran_peer_supports_osmux) {
343 cl->rtp[dir]->use_osmux = true;
344 cl->rtp[dir]->remote_osmux_cid = -1; /* wildcard */
345 }
Neels Hofmeyr62bfa372022-10-31 18:51:07 +0100346 if (codecs_if_known)
347 rtp_stream_set_codecs(cl->rtp[dir], codecs_if_known);
Neels Hofmeyr84ce2062019-10-05 05:15:25 +0200348 if (remote_addr_if_known && osmo_sockaddr_str_is_nonzero(remote_addr_if_known))
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100349 rtp_stream_set_remote_addr(cl->rtp[dir], remote_addr_if_known);
350 return rtp_stream_ensure_ci(cl->rtp[dir], cl->mgw_endpoint);
351}
352
353int call_leg_local_bridge(struct call_leg *cl1, uint32_t call_id1, struct gsm_trans *trans1,
354 struct call_leg *cl2, uint32_t call_id2, struct gsm_trans *trans2)
355{
Neels Hofmeyrb5fe9732023-09-13 22:41:29 +0200356 struct sdp_audio_codecs *cn_codecs = NULL;
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100357
358 cl1->local_bridge = cl2;
359 cl2->local_bridge = cl1;
360
Neels Hofmeyrb5fe9732023-09-13 22:41:29 +0200361 /* Marry the two CN sides of the call legs. Call establishment should have made all efforts for these to be
362 * compatible. However, for local bridging, the codecs and payload type numbers must be exactly identical on
363 * both sides. Both sides may so far have different payload type numbers or slightly differing codecs, but it
364 * will only work when the SDP on the RTP_TO_CN sides of the call legs talk the same payload type numbers.
365 * So, simply take the SDP from one RTP_TO_CN side, and overwrite the other RTP_TO_CN side's SDP with it.
366 * If all goes to plan, the codecs will be identical, or possibly the MGW will do a conversion like AMR-BE to
367 * AMR-OA. In the worst case, the other call leg cannot transcode, and the call fails -- because codec
368 * negotiation did not do a good enough job.
369 *
370 * Copy one call leg's CN config to the other:
371 *
372 * call leg 1 call leg 2
373 * ---MGW-ep------- ---MGW-ep-------
374 * RAN CN CN RAN
375 * AMR:112 AMR:112 AMR:96 AMR:96
376 * |
377 * +-------+
378 * |
379 * V
380 * AMR:112 AMR:112 AMR:112 AMR:96
381 * ^MGW-endpoint converts payload type numbers between 112 and 96.
382 */
383 if (cl1->rtp[RTP_TO_CN] && cl1->rtp[RTP_TO_CN]->codecs_known)
384 cn_codecs = &cl1->rtp[RTP_TO_CN]->codecs;
385 else if (cl2->rtp[RTP_TO_CN] && cl2->rtp[RTP_TO_CN]->codecs_known)
386 cn_codecs = &cl2->rtp[RTP_TO_CN]->codecs;
387 if (!cn_codecs) {
388 LOG_CALL_LEG(cl1, LOGL_ERROR, "RAN-side CN stream codec is not known, not ready for bridging\n");
389 LOG_CALL_LEG(cl2, LOGL_ERROR, "RAN-side CN stream codec is not known, not ready for bridging\n");
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100390 return -EINVAL;
391 }
Pau Espin Pedroldd262262022-01-13 14:06:44 +0100392
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100393 call_leg_ensure_ci(cl1, RTP_TO_CN, call_id1, trans1,
Neels Hofmeyrb5fe9732023-09-13 22:41:29 +0200394 cn_codecs, &cl2->rtp[RTP_TO_CN]->local);
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100395 call_leg_ensure_ci(cl2, RTP_TO_CN, call_id2, trans2,
Neels Hofmeyrb5fe9732023-09-13 22:41:29 +0200396 cn_codecs, &cl1->rtp[RTP_TO_CN]->local);
Neels Hofmeyrc4628a32018-12-07 14:47:34 +0100397 return 0;
398}