blob: 6fbfa4d987c516bb4bd8023dea949af3d93c2767 [file] [log] [blame]
Neels Hofmeyr538d2c52019-01-28 03:51:35 +01001/* FSM to manage multiple connections of an MGW endpoint
2 *
3 * (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
4 * All Rights Reserved
5 *
6 * Author: Neels Hofmeyr <neels@hofmeyr.de>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22
23#include <sys/socket.h>
24#include <netinet/in.h>
25#include <arpa/inet.h>
26
27#include <osmocom/core/fsm.h>
28#include <osmocom/core/byteswap.h>
29#include <osmocom/core/tdef.h>
Pau Espin Pedrol729bf3e2020-08-28 19:28:07 +020030#include <osmocom/core/sockaddr_str.h>
Neels Hofmeyr538d2c52019-01-28 03:51:35 +010031
32#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
33
34#define LOG_CI(ci, level, fmt, args...) do { \
35 if (!ci || !ci->ep) \
Pau Espin Pedrol6a5e5ac2021-07-08 18:13:46 +020036 LOGP(DLMGCP, level, "(unknown MGW endpoint) " fmt, ## args); \
Neels Hofmeyr538d2c52019-01-28 03:51:35 +010037 else \
38 LOG_MGCPC_EP(ci->ep, level, "CI[%d] %s%s%s: " fmt, \
39 (int)(ci - ci->ep->ci), \
40 ci->label ? : "-", \
41 ci->mgcp_ci_str[0] ? " CI=" : "", \
42 ci->mgcp_ci_str[0] ? ci->mgcp_ci_str : "", \
43 ## args); \
44 } while(0)
45
46#define LOG_CI_VERB(ci, level, fmt, args...) do { \
47 if (ci->verb_info.addr[0]) \
48 LOG_CI(ci, level, "%s %s:%u: " fmt, \
49 osmo_mgcp_verb_name(ci->verb), ci->verb_info.addr, ci->verb_info.port, \
50 ## args); \
51 else \
52 LOG_CI(ci, level, "%s: " fmt, \
53 osmo_mgcp_verb_name(ci->verb), \
54 ## args); \
55 } while(0)
56
57enum osmo_mgcpc_ep_fsm_state {
58 OSMO_MGCPC_EP_ST_UNUSED = 0,
59 OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE,
60 OSMO_MGCPC_EP_ST_IN_USE,
61};
62
63enum osmo_mgcpc_ep_fsm_event {
64 _OSMO_MGCPC_EP_EV_LAST = 0,
65 /* and MGW response events are allocated dynamically */
66};
67
68#define FIRST_CI_EVENT (_OSMO_MGCPC_EP_EV_LAST + (_OSMO_MGCPC_EP_EV_LAST & 1)) /* rounded up to even nr */
69#define USABLE_CI ((32 - FIRST_CI_EVENT)/2)
70#define EV_TO_CI_IDX(event) ((event - FIRST_CI_EVENT) / 2)
71
72#define CI_EV_SUCCESS(ci) (FIRST_CI_EVENT + (((ci) - ci->ep->ci) * 2))
73#define CI_EV_FAILURE(ci) (CI_EV_SUCCESS(ci) + 1)
74
75static struct osmo_fsm osmo_mgcpc_ep_fsm;
76
Neels Hofmeyr3ff71282019-10-29 17:41:20 +010077struct fsm_notify {
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +010078 struct llist_head entry;
Neels Hofmeyr3ff71282019-10-29 17:41:20 +010079 struct osmo_fsm_inst *fi;
80 uint32_t success;
81 uint32_t failure;
82 void *data;
83};
84
Neels Hofmeyr538d2c52019-01-28 03:51:35 +010085/*! One connection on an endpoint, corresponding to a connection identifier (CI) as returned by the MGW.
86 * An endpoint has a fixed number of slots of these, which may or may not be in use.
87 */
88struct osmo_mgcpc_ep_ci {
89 struct osmo_mgcpc_ep *ep;
90
91 bool occupied;
92 char label[64];
93 struct osmo_fsm_inst *mgcp_client_fi;
94
95 bool pending;
96 bool sent;
97 enum mgcp_verb verb;
98 struct mgcp_conn_peer verb_info;
Neels Hofmeyr3ff71282019-10-29 17:41:20 +010099 struct fsm_notify notify;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100100
101 bool got_port_info;
102 struct mgcp_conn_peer rtp_info;
103 char mgcp_ci_str[MGCP_CONN_ID_LENGTH];
104};
105
106/*! An MGW endpoint with N connections, like "rtpbridge/23@mgw". */
107struct osmo_mgcpc_ep {
108 /*! MGCP client connection to the MGW. */
109 struct mgcp_client *mgcp_client;
110 struct osmo_fsm_inst *fi;
111
112 /*! Endpoint string; at first this might be a wildcard, and upon the first CRCX OK response, this will reflect
113 * the endpoint name returned by the MGW. */
114 char endpoint[MGCP_ENDPOINT_MAXLEN];
115
116 /*! Timeout definitions used for this endpoint, see osmo_mgcpc_ep_fsm_timeouts. */
117 const struct osmo_tdef *T_defs;
118
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200119 /*! True as soon as the first CRCX OK is received. The endpoint name may be determined by the first CRCX
120 * response, so to dispatch any other messages, the FSM instance *must* wait for the first CRCX OK to arrive
121 * first. Once the endpoint name is pinpointed, any amount of operations may be dispatched concurrently. */
122 bool first_crcx_complete;
123
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100124 /*! Endpoint connection slots. Note that each connection has its own set of FSM event numbers to signal success
125 * and failure, depending on its index within this array. See CI_EV_SUCCESS and CI_EV_FAILURE. */
126 struct osmo_mgcpc_ep_ci ci[USABLE_CI];
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100127
128 /*! Internal use: if a function keeps an fsm_notify for later dispatch while already clearing or re-using the
129 * ci[], the fsm_notify should be kept here to also get canceled by osmo_mgcpc_ep_cancel_notify(). */
130 struct llist_head background_notify;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100131};
132
133const struct value_string osmo_mgcp_verb_names[] = {
134 { MGCP_VERB_CRCX, "CRCX" },
135 { MGCP_VERB_MDCX, "MDCX" },
136 { MGCP_VERB_DLCX, "DLCX" },
137 { MGCP_VERB_AUEP, "AUEP" },
138 { MGCP_VERB_RSIP, "RSIP" },
139 {}
140};
141
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200142static void osmo_mgcpc_ep_count(struct osmo_mgcpc_ep *ep, int *occupied, int *pending_not_sent,
143 int *waiting_for_response);
144
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100145static struct osmo_mgcpc_ep_ci *osmo_mgcpc_ep_check_ci(struct osmo_mgcpc_ep_ci *ci)
146{
147 if (!ci)
148 return NULL;
149 if (!ci->ep)
150 return NULL;
151 if (ci < ci->ep->ci || ci >= &ci->ep->ci[USABLE_CI])
152 return NULL;
153 return ci;
154}
155
156static struct osmo_mgcpc_ep_ci *osmo_mgcpc_ep_ci_for_event(struct osmo_mgcpc_ep *ep, uint32_t event)
157{
158 int idx;
159 if (event < FIRST_CI_EVENT)
160 return NULL;
161 idx = EV_TO_CI_IDX(event);
162 if (idx >= sizeof(ep->ci))
163 return NULL;
164 return osmo_mgcpc_ep_check_ci(&ep->ci[idx]);
165}
166
167const char *osmo_mgcpc_ep_name(const struct osmo_mgcpc_ep *ep)
168{
169 if (!ep)
170 return "NULL";
171 if (ep->endpoint[0])
172 return ep->endpoint;
173 return osmo_fsm_inst_name(ep->fi);
174}
175
Pau Espin Pedrol6225b5c2023-09-22 20:00:18 +0200176/*! Get "local endpoint name" part of the endpoint name: (local-endpoint-name@domain-name)
177 *
178 * \param ep The MGCP Endpoint
179 * \returns the local endpoint name if found, NULL on error.
180 */
181const char *osmo_mgcpc_ep_local_name(const struct osmo_mgcpc_ep *ep)
182{
183 static char buf[1024];
184 const char *sep;
185
186 OSMO_ASSERT(ep);
187 sep = strchr(ep->endpoint, '@');
188 if (!sep) {
189 OSMO_STRLCPY_ARRAY(buf, ep->endpoint);
190 return buf;
191 }
192 if (sep - ep->endpoint >= sizeof(buf))
193 return NULL;
194
195 memcpy(buf, ep->endpoint, sep - ep->endpoint);
196 buf[sep - ep->endpoint] = '\0';
197 return buf;
198}
199
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100200const char *mgcp_conn_peer_name(const struct mgcp_conn_peer *info)
201{
202 /* I'd be fine with a smaller buffer and accept truncation, but gcc possibly refuses to build if
203 * this buffer is too small. */
204 static char buf[1024];
205
206 if (!info)
207 return "NULL";
208
209 if (info->endpoint[0]
210 && info->addr[0])
211 snprintf(buf, sizeof(buf), "%s:%s:%u",
212 info->endpoint, info->addr, info->port);
213 else if (info->endpoint[0])
214 snprintf(buf, sizeof(buf), "%s", info->endpoint);
215 else if (info->addr[0])
216 snprintf(buf, sizeof(buf), "%s:%u", info->addr, info->port);
217 else
218 return "empty";
219 return buf;
220}
221
222const char *osmo_mgcpc_ep_ci_name(const struct osmo_mgcpc_ep_ci *ci)
223{
224 const struct mgcp_conn_peer *rtp_info;
225
226 if (!ci)
227 return "NULL";
228
229 rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(ci);
230
231 if (rtp_info)
232 return mgcp_conn_peer_name(rtp_info);
233 return osmo_mgcpc_ep_name(ci->ep);
234}
235
236const char *osmo_mgcpc_ep_ci_id(const struct osmo_mgcpc_ep_ci *ci)
237{
238 if (!ci || !ci->mgcp_ci_str[0])
239 return NULL;
240 return ci->mgcp_ci_str;
241}
242
Philipp Maier3f4a4cb2021-07-26 13:20:05 +0200243struct mgcp_client *osmo_mgcpc_ep_client(const struct osmo_mgcpc_ep *ep)
244{
245 if (!ep)
246 return NULL;
247 return ep->mgcp_client;
248}
249
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100250static struct value_string osmo_mgcpc_ep_fsm_event_names[33] = {};
251
252static char osmo_mgcpc_ep_fsm_event_name_bufs[32][32] = {};
253
Harald Welte9befdeb2022-11-03 11:41:05 +0100254static void fill_event_names(void)
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100255{
256 int i;
257 for (i = 0; i < (ARRAY_SIZE(osmo_mgcpc_ep_fsm_event_names) - 1); i++) {
258 if (i < _OSMO_MGCPC_EP_EV_LAST)
259 continue;
260 if (i < FIRST_CI_EVENT || EV_TO_CI_IDX(i) > USABLE_CI) {
261 osmo_mgcpc_ep_fsm_event_names[i] = (struct value_string){i, "Unused"};
262 continue;
263 }
264 snprintf(osmo_mgcpc_ep_fsm_event_name_bufs[i], sizeof(osmo_mgcpc_ep_fsm_event_name_bufs[i]),
265 "MGW Response for CI #%d", EV_TO_CI_IDX(i));
266 osmo_mgcpc_ep_fsm_event_names[i] = (struct value_string){i, osmo_mgcpc_ep_fsm_event_name_bufs[i]};
267 }
268}
269
Harald Welte9befdeb2022-11-03 11:41:05 +0100270static __attribute__((constructor)) void osmo_mgcpc_ep_fsm_init(void)
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100271{
272 OSMO_ASSERT(osmo_fsm_register(&osmo_mgcpc_ep_fsm) == 0);
273 fill_event_names();
274}
275
276struct osmo_mgcpc_ep *osmo_mgcpc_ep_fi_mgwep(struct osmo_fsm_inst *fi)
277{
278 OSMO_ASSERT(fi);
279 OSMO_ASSERT(fi->fsm == &osmo_mgcpc_ep_fsm);
280 OSMO_ASSERT(fi->priv);
281 return fi->priv;
282}
283
284/*! Allocate an osmo_mgcpc_ep FSM.
285 * MGCP messages to set up the endpoint will be sent on the given mgcp_client, as soon as the first
286 * osmo_mgcpc_ep_ci_request() is invoked.
287 *
Neels Hofmeyrca2aec02019-10-04 22:47:31 +0200288 * IMPORTANT: To avoid use-after-free problems, using this FSM requires use of deferred FSM deallocation using
289 * osmo_fsm_set_dealloc_ctx(), e.g. using osmo_select_main_ctx(OTC_SELECT) with osmo_select_main_ctx() as main loop.
290 *
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100291 * A typical sequence of events would be:
292 *
293 * ep = osmo_mgcpc_ep_alloc(..., mgcp_client_rtpbridge_wildcard(client));
294 * ci_to_ran = osmo_mgcpc_ep_ci_add(ep);
295 * osmo_mgcpc_ep_ci_request(ci_to_ran, MGCP_VERB_CRCX, verb_info,
296 * my_call_fsm, MY_EVENT_MGCP_OK, MY_EVENT_MGCP_FAIL);
297 * ci_to_cn = osmo_mgcpc_ep_ci_add(ep);
298 * osmo_mgcpc_ep_ci_request(ci_to_cn, MGCP_VERB_CRCX, verb_info,
299 * my_call_fsm, MY_EVENT_MGCP_OK, MY_EVENT_MGCP_FAIL);
300 * ...
301 * osmo_mgcpc_ep_ci_request(ci_to_ran, MGCP_VERB_MDCX, ...);
302 * ...
303 * osmo_mgcpc_ep_clear(ep);
304 * ep = NULL;
305 *
306 * \param parent Parent FSM.
307 * \param parent_term_event Event to dispatch to the parent on termination of this FSM instance.
308 * \param mgcp_client Connection to the MGW.
309 * \param T_defs Timeout definitions to be used for FSM states, see osmo_mgcpc_ep_fsm_timeouts.
310 * \param fsm_id FSM instance ID.
311 * \param endpoint_str_fmt The endpoint string format to send to the MGW upon the first CRCX.
312 * See mgcp_client_rtpbridge_wildcard() for "rtpbridge" endpoints.
313 */
314struct osmo_mgcpc_ep *osmo_mgcpc_ep_alloc(struct osmo_fsm_inst *parent, uint32_t parent_term_event,
315 struct mgcp_client *mgcp_client,
316 const struct osmo_tdef *T_defs,
317 const char *fsm_id,
318 const char *endpoint_str_fmt, ...)
319{
320 va_list ap;
321 struct osmo_fsm_inst *fi;
322 struct osmo_mgcpc_ep *ep;
323 int rc;
324
325 if (!mgcp_client)
326 return NULL;
327
328 fi = osmo_fsm_inst_alloc_child(&osmo_mgcpc_ep_fsm, parent, parent_term_event);
329 OSMO_ASSERT(fi);
330
331 osmo_fsm_inst_update_id(fi, fsm_id);
332
333 ep = talloc_zero(fi, struct osmo_mgcpc_ep);
334 OSMO_ASSERT(ep);
335
336 *ep = (struct osmo_mgcpc_ep){
337 .mgcp_client = mgcp_client,
338 .fi = fi,
339 .T_defs = T_defs,
340 };
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100341 INIT_LLIST_HEAD(&ep->background_notify);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100342 fi->priv = ep;
343
344 va_start(ap, endpoint_str_fmt);
345 rc = vsnprintf(ep->endpoint, sizeof(ep->endpoint), endpoint_str_fmt ? : "", ap);
346 va_end(ap);
347
348 if (rc <= 0 || rc >= sizeof(ep->endpoint)) {
349 LOG_MGCPC_EP(ep, LOGL_ERROR, "Endpoint name too long or too short: %s\n",
350 ep->endpoint);
351 osmo_fsm_inst_term(ep->fi, OSMO_FSM_TERM_ERROR, 0);
352 return NULL;
353 }
354
355 return ep;
356}
357
358/*! Add a connection to an endpoint.
359 * Allocate a connection identifier slot in the osmo_mgcpc_ep instance, do not yet dispatch a CRCX.
360 * The CRCX is dispatched only upon the first osmo_mgcpc_ep_ci_request().
361 * \param ep Parent endpoint instance.
362 * \param label_fmt Label for logging.
363 */
364struct osmo_mgcpc_ep_ci *osmo_mgcpc_ep_ci_add(struct osmo_mgcpc_ep *ep,
365 const char *label_fmt, ...)
366{
367 va_list ap;
368 int i;
369 struct osmo_mgcpc_ep_ci *ci;
370
371 for (i = 0; i < USABLE_CI; i++) {
372 ci = &ep->ci[i];
373
374 if (ci->occupied || ci->mgcp_client_fi)
375 continue;
376
377 *ci = (struct osmo_mgcpc_ep_ci){
378 .ep = ep,
379 .occupied = true,
380 };
381 if (label_fmt) {
382 va_start(ap, label_fmt);
383 vsnprintf(ci->label, sizeof(ci->label), label_fmt, ap);
384 va_end(ap);
385 }
386 return ci;
387 }
388
389 LOG_MGCPC_EP(ep, LOGL_ERROR,
390 "Cannot allocate another endpoint, all "
391 OSMO_STRINGIFY_VAL(USABLE_CI) " are in use\n");
392
393 return NULL;
394}
395
396static bool osmo_mgcpc_ep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi);
397
398static void on_failure(struct osmo_mgcpc_ep_ci *ci)
399{
Neels Hofmeyrcc0b97e2019-10-01 19:44:10 +0200400 struct osmo_mgcpc_ep *ep = ci->ep;
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100401 struct fsm_notify notify;
Neels Hofmeyrcc0b97e2019-10-01 19:44:10 +0200402 int i;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100403
404 if (!ci->occupied)
405 return;
406
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100407 /* When dispatching an event for this CI, the user may decide to trigger the next request for this conn right
408 * away. So we must be ready with a cleared *ci. Store the notify separately and clear before dispatching. */
409 notify = ci->notify;
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100410 /* Register the planned notification in ep->background_notify so we also catch any osmo_mgcpc_ep_cancel_notify()
411 * that might be triggered between clearing the ci and actually dispatching the event. */
412 llist_add(&notify.entry, &ep->background_notify);
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100413
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100414 *ci = (struct osmo_mgcpc_ep_ci){
415 .ep = ci->ep,
416 };
417
Neels Hofmeyrcc0b97e2019-10-01 19:44:10 +0200418 /* An MGCP failure typically means the endpoint becomes unusable, cancel all pending request (except DLCX).
419 * Particularly, if two CRCX were scheduled and the first fails, we must no longer dispatch the second CRCX. */
420 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
421 struct osmo_mgcpc_ep_ci *other_ci = &ep->ci[i];
422 if (other_ci == ci)
423 continue;
424 if (!other_ci->occupied)
425 continue;
426 if (!other_ci->pending)
427 continue;
428 if (other_ci->sent)
429 continue;
430 if (other_ci->verb == MGCP_VERB_DLCX)
431 continue;
432 /* Just clear the pending request, don't fire more events than below. */
433 other_ci->pending = false;
434 }
435
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100436 /* If this check has terminated the FSM instance, don't fire any more events to prevent use-after-free problems.
437 * The endpoint FSM does dispatch a term event to its parent, and everything should be cleaned like that. */
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100438 if (!osmo_mgcpc_ep_fsm_check_state_chg_after_response(ep->fi)) {
439 /* The ep has deallocated, no need to llist_del(&notify.entry) here. */
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100440 return;
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100441 }
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100442
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100443 if (notify.fi)
444 osmo_fsm_inst_dispatch(notify.fi, notify.failure, notify.data);
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100445
446 llist_del(&notify.entry);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100447}
448
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200449static int update_endpoint_name(struct osmo_mgcpc_ep_ci *ci, const char *new_endpoint_name)
450{
451 struct osmo_mgcpc_ep *ep = ci->ep;
452 int rc;
453 int i;
454
455 if (!strcmp(ep->endpoint, new_endpoint_name)) {
456 /* Same endpoint name, nothing to do. */
457 return 0;
458 }
459
460 /* The endpoint name should only change on the very first CRCX response. */
461 if (ep->first_crcx_complete) {
462 LOG_CI(ci, LOGL_ERROR, "Reponse returned mismatching endpoint name."
463 " This is endpoint %s, instead received %s\n",
464 ep->endpoint, new_endpoint_name);
465 on_failure(ci);
466 return -EINVAL;
467 }
468
469 /* This is the first CRCX response, update endpoint name. */
470 rc = OSMO_STRLCPY_ARRAY(ep->endpoint, new_endpoint_name);
471 if (rc <= 0 || rc >= sizeof(ep->endpoint)) {
472 LOG_CI(ci, LOGL_ERROR, "Unable to copy endpoint name %s\n", osmo_quote_str(new_endpoint_name, -1));
473 osmo_mgcpc_ep_ci_dlcx(ci);
474 on_failure(ci);
475 return -ENOSPC;
476 }
477
478 /* Make sure already pending requests use this updated endpoint name. */
479 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
480 struct osmo_mgcpc_ep_ci *other_ci = &ep->ci[i];
481 if (!other_ci->occupied)
482 continue;
483 if (!other_ci->pending)
484 continue;
485 if (other_ci->sent)
486 continue;
487 OSMO_STRLCPY_ARRAY(other_ci->verb_info.endpoint, ep->endpoint);
488 }
489 return 0;
490}
491
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100492static void on_success(struct osmo_mgcpc_ep_ci *ci, void *data)
493{
494 struct mgcp_conn_peer *rtp_info;
495
496 if (!ci->occupied)
497 return;
498
499 ci->pending = false;
500
Pau Espin Pedrol2741d6b2020-09-02 19:31:17 +0200501 rtp_info = data;
502
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100503 switch (ci->verb) {
504 case MGCP_VERB_CRCX:
505 /* If we sent a wildcarded endpoint name on CRCX, we need to store the resulting endpoint
506 * name here. Also, we receive the MGW's RTP port information. */
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100507 osmo_strlcpy(ci->mgcp_ci_str, mgcp_conn_get_ci(ci->mgcp_client_fi),
508 sizeof(ci->mgcp_ci_str));
509 if (rtp_info->endpoint[0]) {
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200510 /* On errors, this instance might already be deallocated. Make sure to not access anything after
511 * error. */
512 if (update_endpoint_name(ci, rtp_info->endpoint))
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100513 return;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100514 }
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200515 ci->ep->first_crcx_complete = true;
Pau Espin Pedrol2741d6b2020-09-02 19:31:17 +0200516 OSMO_ASSERT(rtp_info);
517 /* fall through */
518 case MGCP_VERB_MDCX:
519 /* Always update the received RTP ip/port information, since MGW
520 * may provide new one after remote end params changed */
521 if (rtp_info) {
522 ci->got_port_info = true;
523 ci->rtp_info = *rtp_info;
524 }
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100525 break;
526
527 default:
528 break;
529 }
530
531 LOG_CI(ci, LOGL_DEBUG, "received successful response to %s: RTP=%s%s\n",
532 osmo_mgcp_verb_name(ci->verb),
533 mgcp_conn_peer_name(ci->got_port_info? &ci->rtp_info : NULL),
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100534 ci->notify.fi ? "" : " (not sending a notification)");
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100535
Neels Hofmeyr43eed632023-12-12 23:57:39 +0100536 /* Below ordering is a delicate decision:
537 *
538 * We want to
539 * - emit the resulting event to ci->notify.fi,
540 * - check whether we want to tx the next pending MGCP message.
541 * Both these steps may terminate (=deallocate) the ep.
542 * So whichever one goes first may cause a use-after-free in the other.
543 *
544 * When dispatching the FSM event, we don't get an rc indicating dealloc of the FSM -- it may deallocate and we
545 * cannot tell. The common mechanism for that is osmo_fsm_set_dealloc_ctx(OTC_SELECT) and query the still
546 * allocated FSM state after termination (here we would check 'if (ci->ep != NULL)'), but we cannot assume the
547 * caller has actually set up an osmo_fsm_set_dealloc_ctx(). At time of writing, e.g. osmo-hnbgw does not use
548 * it.
549 *
550 * In osmo_mgcpc_ep_fsm_check_state_chg_after_response(), we do get an rc: false means FSM has terminated.
551 * On termination, the ep emits a term event to the FSM's parent.
552 * That may cause the notify.fi to be terminated in turn, depending on how the caller set things up.
553 * So: we cannot store notify.fi before, then call osmo_mgcpc_ep_fsm_check_state_chg_after_response(), and then
554 * emit the event, because notify.fi may have deallocated. We cannot look up whether
555 * osmo_mgcpc_ep_cancel_notify() has been called, because ci may have deallocated along with ci->ep.
556 *
557 * We have to skip emitting below success event in case the ep is now terminated.
558 * - It may be the final DLCX OK: not a problem, osmo_mgcpc_ep_ci_dlcx() has no notify args on purpose, so we do
559 * make all callers not set a notify event for DLCX by design. notify.fi should always be NULL when the final
560 * DLCX OK terminates the local endpoint state.
561 * - It may also be sudden termination due to a bad problem, in which case we shouldn't emit success.
562 * The osmo_fsm_inst.parent_term_event should suffice as feedback to the caller.
563 */
564
565 if (osmo_mgcpc_ep_fsm_check_state_chg_after_response(ci->ep->fi) == false) {
566 /* false means, the ci->ep has been terminated. */
567 return;
568 }
569
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100570 if (ci->notify.fi)
571 osmo_fsm_inst_dispatch(ci->notify.fi, ci->notify.success, ci->notify.data);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100572}
573
Neels Hofmeyr59e7cf42021-05-01 02:31:01 +0000574/*! Return the MGW's local RTP port information for this connection, i.e. the local port that MGW is receiving on, as
575 * returned by the last CRCX-OK / MDCX-OK message. */
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100576const struct mgcp_conn_peer *osmo_mgcpc_ep_ci_get_rtp_info(const struct osmo_mgcpc_ep_ci *ci)
577{
578 ci = osmo_mgcpc_ep_check_ci((struct osmo_mgcpc_ep_ci*)ci);
579 if (!ci)
580 return NULL;
581 if (!ci->got_port_info)
582 return NULL;
583 return &ci->rtp_info;
584}
585
Neels Hofmeyr59e7cf42021-05-01 02:31:01 +0000586/*! Return the MGW's remote RTP port information for this connection, i.e. the remote RTP port that the MGW is sending
587 * to, as sent to the MGW by the last CRCX / MDCX message. */
588const struct mgcp_conn_peer *osmo_mgcpc_ep_ci_get_remote_rtp_info(const struct osmo_mgcpc_ep_ci *ci)
589{
590 ci = osmo_mgcpc_ep_check_ci((struct osmo_mgcpc_ep_ci*)ci);
591 if (!ci)
592 return NULL;
593 return &ci->verb_info;
594}
595
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100596/*! Return the MGW's RTP port information for this connection, as returned by the last CRCX/MDCX OK message. */
597bool osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(const struct osmo_mgcpc_ep_ci *ci, struct sockaddr_storage *dest)
598{
599 const struct mgcp_conn_peer *rtp_info;
Pau Espin Pedrol729bf3e2020-08-28 19:28:07 +0200600 int family;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100601 struct sockaddr_in *sin;
Pau Espin Pedrol729bf3e2020-08-28 19:28:07 +0200602 struct sockaddr_in6 *sin6;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100603
604 rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(ci);
605 if (!rtp_info)
606 return false;
607
Pau Espin Pedrol729bf3e2020-08-28 19:28:07 +0200608 family = osmo_ip_str_type(rtp_info->addr);
609 switch (family) {
610 case AF_INET:
611 sin = (struct sockaddr_in *)dest;
612 sin->sin_family = AF_INET;
613 sin->sin_port = osmo_ntohs(rtp_info->port);
614 if (inet_pton(AF_INET, rtp_info->addr, &sin->sin_addr) != 1)
615 return false;
616 break;
617 case AF_INET6:
618 sin6 = (struct sockaddr_in6 *)dest;
619 sin6->sin6_family = AF_INET6;
620 sin6->sin6_port = osmo_ntohs(rtp_info->port);
621 if (inet_pton(AF_INET6, rtp_info->addr, &sin6->sin6_addr) != 1)
622 return false;
623 break;
624 default:
625 return false;
626 }
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100627 return true;
628}
629
Pau Espin Pedrol30907dc2019-05-06 11:54:17 +0200630bool osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(const struct osmo_mgcpc_ep_ci *ci, uint8_t* cid)
631{
632 const struct mgcp_conn_peer *rtp_info;
633
634 rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(ci);
635 if (!rtp_info)
636 return false;
637
638 if (!rtp_info->x_osmo_osmux_use)
639 return false;
640
641 *cid = rtp_info->x_osmo_osmux_cid;
642 return true;
643}
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100644
645static const struct osmo_tdef_state_timeout osmo_mgcpc_ep_fsm_timeouts[32] = {
Neels Hofmeyrfbf07f32020-09-15 23:59:23 +0200646 [OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE] = { .T=-2427 },
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100647};
648
649/* Transition to a state, using the T timer defined in assignment_fsm_timeouts.
650 * The actual timeout value is in turn obtained from osmo_mgcpc_ep.T_defs.
651 * Assumes local variable fi exists. */
652#define osmo_mgcpc_ep_fsm_state_chg(state) \
653 osmo_tdef_fsm_inst_state_chg(fi, state, osmo_mgcpc_ep_fsm_timeouts, \
654 ((struct osmo_mgcpc_ep*)fi->priv)->T_defs, 5)
655
656/*! Dispatch an actual CRCX/MDCX/DLCX message for this connection.
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100657 *
658 * If the 'notify' instance deallocates before it received a notification of event_success or event_failure,
659 * osmo_mgcpc_ep_ci_cancel_notify() or osmo_mgcpc_ep_cancel_notify() must be called. It is not harmful to cancel
660 * notification after an event has been received.
661 *
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100662 * \param ci Connection identifier as obtained from osmo_mgcpc_ep_ci_add().
663 * \param verb MGCP operation to dispatch.
664 * \param verb_info Parameters for the MGCP operation.
665 * \param notify Peer FSM instance to notify of completed/failed operation.
666 * \param event_success Which event to dispatch to 'notify' upon OK response.
667 * \param event_failure Which event to dispatch to 'notify' upon failure response.
668 * \param notify_data Data pointer to pass to the event dispatch for both success and failure.
669 */
670void osmo_mgcpc_ep_ci_request(struct osmo_mgcpc_ep_ci *ci,
671 enum mgcp_verb verb, const struct mgcp_conn_peer *verb_info,
672 struct osmo_fsm_inst *notify,
673 uint32_t event_success, uint32_t event_failure,
674 void *notify_data)
675{
676 struct osmo_mgcpc_ep *ep;
677 struct osmo_fsm_inst *fi;
678 struct osmo_mgcpc_ep_ci cleared_ci;
679 ci = osmo_mgcpc_ep_check_ci(ci);
680
681 if (!ci) {
Pau Espin Pedrol6a5e5ac2021-07-08 18:13:46 +0200682 LOGP(DLMGCP, LOGL_ERROR, "Invalid MGW endpoint request: no ci\n");
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100683 goto dispatch_error;
684 }
685 if (!verb_info && verb != MGCP_VERB_DLCX) {
686 LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: missing verb details for %s\n",
687 osmo_mgcp_verb_name(verb));
688 goto dispatch_error;
689 }
690 if ((verb < 0) || (verb > MGCP_VERB_RSIP)) {
691 LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: unknown verb: %s\n",
692 osmo_mgcp_verb_name(verb));
693 goto dispatch_error;
694 }
695
696 ep = ci->ep;
697 fi = ep->fi;
698
699 /* Clear volatile state by explicitly keeping those that should remain. Because we can't assign
700 * the char[] directly, dance through cleared_ci and copy back. */
701 cleared_ci = (struct osmo_mgcpc_ep_ci){
702 .ep = ep,
703 .mgcp_client_fi = ci->mgcp_client_fi,
704 .got_port_info = ci->got_port_info,
705 .rtp_info = ci->rtp_info,
706
707 .occupied = true,
708 /* .pending = true follows below */
709 .verb = verb,
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100710 .notify = {
711 .fi = notify,
712 .success = event_success,
713 .failure = event_failure,
714 .data = notify_data,
715 }
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100716 };
717 osmo_strlcpy(cleared_ci.label, ci->label, sizeof(cleared_ci.label));
718 osmo_strlcpy(cleared_ci.mgcp_ci_str, ci->mgcp_ci_str, sizeof(cleared_ci.mgcp_ci_str));
719 *ci = cleared_ci;
720
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100721 LOG_CI_VERB(ci, LOGL_DEBUG, "notify=%s\n", osmo_fsm_inst_name(ci->notify.fi));
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100722
723 if (verb_info)
724 ci->verb_info = *verb_info;
725
726 if (ep->endpoint[0]) {
727 if (ci->verb_info.endpoint[0] && strcmp(ci->verb_info.endpoint, ep->endpoint))
728 LOG_CI(ci, LOGL_ERROR,
729 "Warning: Requested %s on endpoint %s, but this CI is on endpoint %s."
730 " Using the proper endpoint instead.\n",
731 osmo_mgcp_verb_name(verb), ci->verb_info.endpoint, ep->endpoint);
732 osmo_strlcpy(ci->verb_info.endpoint, ep->endpoint, sizeof(ci->verb_info.endpoint));
733 }
734
735 switch (ci->verb) {
736 case MGCP_VERB_CRCX:
737 if (ci->mgcp_client_fi) {
738 LOG_CI(ci, LOGL_ERROR, "CRCX can be called only once per MGW endpoint CI\n");
739 on_failure(ci);
740 return;
741 }
742 break;
743
744 case MGCP_VERB_MDCX:
745 if (!ci->mgcp_client_fi) {
746 LOG_CI_VERB(ci, LOGL_ERROR, "The first verb on an unused MGW endpoint CI must be CRCX, not %s\n",
747 osmo_mgcp_verb_name(ci->verb));
748 on_failure(ci);
749 return;
750 }
751 break;
752
753 case MGCP_VERB_DLCX:
754 if (!ci->mgcp_client_fi) {
755 LOG_CI_VERB(ci, LOGL_DEBUG, "Ignoring DLCX on unused MGW endpoint CI\n");
756 return;
757 }
758 break;
759
760 default:
761 LOG_CI(ci, LOGL_ERROR, "This verb is not supported: %s\n", osmo_mgcp_verb_name(ci->verb));
762 on_failure(ci);
763 return;
764 }
765
766 ci->pending = true;
767
768 LOG_CI_VERB(ci, LOGL_DEBUG, "Scheduling\n");
769
770 if (ep->fi->state != OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE)
771 osmo_mgcpc_ep_fsm_state_chg(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE);
772
773 return;
774dispatch_error:
775 if (notify)
776 osmo_fsm_inst_dispatch(notify, event_failure, notify_data);
777}
778
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100779/*! No longer notify for any state changes for any conns of this endpoint.
780 * Useful if the notify instance passed to osmo_mgcpc_ep_ci_request() is about to deallocate.
781 * \param ep The endpoint FSM instance.
782 * \param notify Which target to cancel notification for, if NULL cancel all notifications. */
783void osmo_mgcpc_ep_cancel_notify(struct osmo_mgcpc_ep *ep, struct osmo_fsm_inst *notify)
784{
785 struct fsm_notify *n;
786 int i;
787 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
788 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
789 if (!notify || ci->notify.fi == notify)
790 ci->notify.fi = NULL;
791 }
792 llist_for_each_entry(n, &ep->background_notify, entry) {
793 if (!notify || n->fi == notify)
794 n->fi = NULL;
795 }
796
797}
798
Neels Hofmeyr923d60b2019-10-29 17:40:08 +0100799/* Return the osmo_mgcpc_ep that this conn belongs to. */
800struct osmo_mgcpc_ep *osmo_mgcpc_ep_ci_ep(struct osmo_mgcpc_ep_ci *conn)
801{
802 if (!conn)
803 return NULL;
804 return conn->ep;
805}
806
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100807static int send_verb(struct osmo_mgcpc_ep_ci *ci)
808{
809 int rc;
810 struct osmo_mgcpc_ep *ep = ci->ep;
Neels Hofmeyr055ded72019-10-29 17:46:30 +0100811 struct fsm_notify notify;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100812
813 if (!ci->occupied || !ci->pending || ci->sent)
814 return 0;
815
816 switch (ci->verb) {
817
818 case MGCP_VERB_CRCX:
819 OSMO_ASSERT(!ci->mgcp_client_fi);
820 LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
821 ci->mgcp_client_fi = mgcp_conn_create(ep->mgcp_client, ep->fi,
822 CI_EV_FAILURE(ci), CI_EV_SUCCESS(ci),
823 &ci->verb_info);
824 ci->sent = true;
825 if (!ci->mgcp_client_fi){
826 LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send\n");
827 on_failure(ci);
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200828 return -EINVAL;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100829 }
830 osmo_fsm_inst_update_id(ci->mgcp_client_fi, ci->label);
831 break;
832
833 case MGCP_VERB_MDCX:
834 OSMO_ASSERT(ci->mgcp_client_fi);
835 LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
836 rc = mgcp_conn_modify(ci->mgcp_client_fi, CI_EV_SUCCESS(ci), &ci->verb_info);
837 ci->sent = true;
838 if (rc) {
839 LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send (rc=%d %s)\n", rc, strerror(-rc));
840 on_failure(ci);
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200841 return -EINVAL;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100842 }
843 break;
844
845 case MGCP_VERB_DLCX:
846 LOG_CI(ci, LOGL_DEBUG, "Sending MGCP: %s %s\n",
847 osmo_mgcp_verb_name(ci->verb), ci->mgcp_ci_str);
848 /* The way this is designed, we actually need to forget all about the ci right away. */
849 mgcp_conn_delete(ci->mgcp_client_fi);
Neels Hofmeyr055ded72019-10-29 17:46:30 +0100850 notify = ci->notify;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100851 *ci = (struct osmo_mgcpc_ep_ci){
852 .ep = ep,
853 };
Neels Hofmeyr055ded72019-10-29 17:46:30 +0100854 /* When dispatching an event for this CI, the user may decide to trigger the next request for this conn
855 * right away. So we must be ready with a cleared *ci. */
856 if (notify.fi)
857 osmo_fsm_inst_dispatch(notify.fi, notify.success, notify.data);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100858 break;
859
860 default:
861 OSMO_ASSERT(false);
862 }
863
864 return 1;
865}
866
867/*! DLCX all connections, terminate the endpoint FSM and free. */
868void osmo_mgcpc_ep_clear(struct osmo_mgcpc_ep *ep)
869{
870 if (!ep)
871 return;
Neels Hofmeyrf2bf8dc2019-10-29 17:39:56 +0100872 osmo_mgcpc_ep_cancel_notify(ep, NULL);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100873 osmo_fsm_inst_term(ep->fi, OSMO_FSM_TERM_REGULAR, 0);
874}
875
876static void osmo_mgcpc_ep_count(struct osmo_mgcpc_ep *ep, int *occupied, int *pending_not_sent,
877 int *waiting_for_response)
878{
879 int i;
880
881 if (occupied)
882 *occupied = 0;
883
884 if (pending_not_sent)
885 *pending_not_sent = 0;
886
887 if (waiting_for_response)
888 *waiting_for_response = 0;
889
890 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
891 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
892 if (ci->occupied) {
893 if (occupied)
894 (*occupied)++;
895 } else
896 continue;
897
898 if (ci->pending)
899 LOG_CI_VERB(ci, LOGL_DEBUG, "%s\n",
900 ci->sent ? "waiting for response" : "waiting to be sent");
901 else
902 LOG_CI_VERB(ci, LOGL_DEBUG, "done (%s)\n", mgcp_conn_peer_name(osmo_mgcpc_ep_ci_get_rtp_info(ci)));
903
904 if (ci->pending && ci->sent)
905 if (waiting_for_response)
906 (*waiting_for_response)++;
907 if (ci->pending && !ci->sent)
908 if (pending_not_sent)
909 (*pending_not_sent)++;
910 }
911}
912
913static bool osmo_mgcpc_ep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi)
914{
915 int waiting_for_response;
916 int occupied;
917 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
918
919 osmo_mgcpc_ep_count(ep, &occupied, NULL, &waiting_for_response);
920 LOG_MGCPC_EP(ep, LOGL_DEBUG, "CI in use: %d, waiting for response: %d\n", occupied, waiting_for_response);
921
922 if (!occupied) {
923 /* All CI have been released. The endpoint no longer exists. Notify the parent FSM, by
924 * terminating. */
925 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
926 return false;
927 }
928
929 if (!waiting_for_response) {
930 if (fi->state != OSMO_MGCPC_EP_ST_IN_USE)
931 osmo_mgcpc_ep_fsm_state_chg(OSMO_MGCPC_EP_ST_IN_USE);
932 }
933
934 return true;
935}
936
937static void osmo_mgcpc_ep_fsm_wait_mgw_response_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
938{
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200939 static int re_enter = 0;
940 int rc;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100941 int count = 0;
942 int i;
943 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
944
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200945 re_enter++;
946 OSMO_ASSERT(re_enter < 10);
947
948 /* The first CRCX gives us the endpoint name in the CRCX response. So we must wait for the first CRCX endpoint
949 * response to come in before sending any other MGCP requests -- otherwise we might end up creating new
950 * endpoints instead of acting on the same. This FSM always sends out N requests and waits for all of them to
951 * complete before sending out new requests. Hence we're safe when the very first time at most one request is
952 * sent (which needs to be a CRCX). */
953
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100954 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200955 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
956
957 /* Make sure that only CRCX get dispatched if no CRCX were sent yet. */
958 if (!ep->first_crcx_complete) {
959 if (ci->occupied && ci->verb != MGCP_VERB_CRCX)
960 continue;
961 }
962
963 rc = send_verb(&ep->ci[i]);
964 /* Need to be careful not to access the instance after failure. Event chains may already have
965 * deallocated this memory. */
966 if (rc < 0)
967 return;
968 if (!rc)
969 continue;
970 count++;
971 /* Make sure that we wait for the first CRCX response before dispatching more requests. */
972 if (!ep->first_crcx_complete)
973 break;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100974 }
975
976 LOG_MGCPC_EP(ep, LOGL_DEBUG, "Sent messages: %d\n", count);
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200977 if (ep->first_crcx_complete)
978 osmo_mgcpc_ep_fsm_check_state_chg_after_response(fi);
979 re_enter--;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100980}
981
982static void osmo_mgcpc_ep_fsm_handle_ci_events(struct osmo_fsm_inst *fi, uint32_t event, void *data)
983{
984 struct osmo_mgcpc_ep_ci *ci;
985 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
986 ci = osmo_mgcpc_ep_ci_for_event(ep, event);
987 if (ci) {
988 if (event == CI_EV_SUCCESS(ci))
989 on_success(ci, data);
990 else
991 on_failure(ci);
992 }
993}
994
995static void osmo_mgcpc_ep_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
996{
997 int pending_not_sent;
998 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
999
1000 osmo_mgcpc_ep_count(ep, NULL, &pending_not_sent, NULL);
1001 if (pending_not_sent)
1002 osmo_mgcpc_ep_fsm_state_chg(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE);
1003}
1004
1005#define S(x) (1 << (x))
1006
1007static const struct osmo_fsm_state osmo_mgcpc_ep_fsm_states[] = {
1008 [OSMO_MGCPC_EP_ST_UNUSED] = {
1009 .name = "UNUSED",
1010 .in_event_mask = 0,
1011 .out_state_mask = 0
1012 | S(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE)
1013 ,
1014 },
1015 [OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE] = {
1016 .name = "WAIT_MGW_RESPONSE",
1017 .onenter = osmo_mgcpc_ep_fsm_wait_mgw_response_onenter,
1018 .action = osmo_mgcpc_ep_fsm_handle_ci_events,
1019 .in_event_mask = 0xffffffff,
1020 .out_state_mask = 0
1021 | S(OSMO_MGCPC_EP_ST_IN_USE)
1022 ,
1023 },
1024 [OSMO_MGCPC_EP_ST_IN_USE] = {
1025 .name = "IN_USE",
1026 .onenter = osmo_mgcpc_ep_fsm_in_use_onenter,
1027 .action = osmo_mgcpc_ep_fsm_handle_ci_events,
1028 .in_event_mask = 0xffffffff, /* mgcp_client_fsm may send parent term anytime */
1029 .out_state_mask = 0
1030 | S(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE)
1031 ,
1032 },
1033};
1034
1035static int osmo_mgcpc_ep_fsm_timer_cb(struct osmo_fsm_inst *fi)
1036{
1037 int i;
1038 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
1039
1040 switch (fi->T) {
1041 default:
1042 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
1043 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
1044 if (!ci->occupied)
1045 continue;
1046 if (!(ci->pending && ci->sent))
1047 continue;
1048 on_failure(ci);
1049 }
1050 return 0;
1051 }
1052
1053 return 0;
1054}
1055
Harald Weltee0e5a772022-06-24 09:45:02 +02001056static void osmo_mgcpc_ep_fsm_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
Neels Hofmeyr03fcc912021-07-15 02:09:42 +02001057{
1058 int i;
1059 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
1060
1061 /* We want the mgcp_client_fsm to still stick around until it received the DLCX "OK" responses from the MGW. So
1062 * it should not dealloc along with this ep_fsm instance. Instead, signal DLCX for each conn on the endpoint,
1063 * and detach the mgcp_client_fsm from being a child-fsm.
1064 *
1065 * After mgcp_conn_delete(), an mgcp_client_fsm instance goes into ST_DLCX_RESP, which waits up to 4 seconds for
1066 * a DLCX OK. If none is received in that time, the instance terminates. So cleanup of the instance is
1067 * guaranteed. */
1068
1069 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
1070 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
1071
1072 if (!ci->occupied || !ci->mgcp_client_fi)
1073 continue;
1074
1075 /* mgcp_conn_delete() unlinks itself from this parent FSM implicitly and waits for the DLCX OK. */
1076 mgcp_conn_delete(ci->mgcp_client_fi);
1077 /* Forget all about this ci */
1078 *ci = (struct osmo_mgcpc_ep_ci){
1079 .ep = ep,
1080 };
1081 }
1082}
1083
Neels Hofmeyr538d2c52019-01-28 03:51:35 +01001084static struct osmo_fsm osmo_mgcpc_ep_fsm = {
Pau Espin Pedrol182ca3b2019-05-08 14:01:20 +02001085 .name = "mgw-endp",
Neels Hofmeyr538d2c52019-01-28 03:51:35 +01001086 .states = osmo_mgcpc_ep_fsm_states,
1087 .num_states = ARRAY_SIZE(osmo_mgcpc_ep_fsm_states),
1088 .log_subsys = DLMGCP,
1089 .event_names = osmo_mgcpc_ep_fsm_event_names,
1090 .timer_cb = osmo_mgcpc_ep_fsm_timer_cb,
Neels Hofmeyr03fcc912021-07-15 02:09:42 +02001091 .pre_term = osmo_mgcpc_ep_fsm_pre_term,
Neels Hofmeyr538d2c52019-01-28 03:51:35 +01001092};