blob: 30ad2d359106afb1c66ae0f6bd95e9b4d531a85c [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>
30
31#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
32
33#define LOG_CI(ci, level, fmt, args...) do { \
34 if (!ci || !ci->ep) \
35 LOGP(DLGLOBAL, level, "(unknown MGW endpoint) " fmt, ## args); \
36 else \
37 LOG_MGCPC_EP(ci->ep, level, "CI[%d] %s%s%s: " fmt, \
38 (int)(ci - ci->ep->ci), \
39 ci->label ? : "-", \
40 ci->mgcp_ci_str[0] ? " CI=" : "", \
41 ci->mgcp_ci_str[0] ? ci->mgcp_ci_str : "", \
42 ## args); \
43 } while(0)
44
45#define LOG_CI_VERB(ci, level, fmt, args...) do { \
46 if (ci->verb_info.addr[0]) \
47 LOG_CI(ci, level, "%s %s:%u: " fmt, \
48 osmo_mgcp_verb_name(ci->verb), ci->verb_info.addr, ci->verb_info.port, \
49 ## args); \
50 else \
51 LOG_CI(ci, level, "%s: " fmt, \
52 osmo_mgcp_verb_name(ci->verb), \
53 ## args); \
54 } while(0)
55
56enum osmo_mgcpc_ep_fsm_state {
57 OSMO_MGCPC_EP_ST_UNUSED = 0,
58 OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE,
59 OSMO_MGCPC_EP_ST_IN_USE,
60};
61
62enum osmo_mgcpc_ep_fsm_event {
63 _OSMO_MGCPC_EP_EV_LAST = 0,
64 /* and MGW response events are allocated dynamically */
65};
66
67#define FIRST_CI_EVENT (_OSMO_MGCPC_EP_EV_LAST + (_OSMO_MGCPC_EP_EV_LAST & 1)) /* rounded up to even nr */
68#define USABLE_CI ((32 - FIRST_CI_EVENT)/2)
69#define EV_TO_CI_IDX(event) ((event - FIRST_CI_EVENT) / 2)
70
71#define CI_EV_SUCCESS(ci) (FIRST_CI_EVENT + (((ci) - ci->ep->ci) * 2))
72#define CI_EV_FAILURE(ci) (CI_EV_SUCCESS(ci) + 1)
73
74static struct osmo_fsm osmo_mgcpc_ep_fsm;
75
Neels Hofmeyr3ff71282019-10-29 17:41:20 +010076struct fsm_notify {
77 struct osmo_fsm_inst *fi;
78 uint32_t success;
79 uint32_t failure;
80 void *data;
81};
82
Neels Hofmeyr538d2c52019-01-28 03:51:35 +010083/*! One connection on an endpoint, corresponding to a connection identifier (CI) as returned by the MGW.
84 * An endpoint has a fixed number of slots of these, which may or may not be in use.
85 */
86struct osmo_mgcpc_ep_ci {
87 struct osmo_mgcpc_ep *ep;
88
89 bool occupied;
90 char label[64];
91 struct osmo_fsm_inst *mgcp_client_fi;
92
93 bool pending;
94 bool sent;
95 enum mgcp_verb verb;
96 struct mgcp_conn_peer verb_info;
Neels Hofmeyr3ff71282019-10-29 17:41:20 +010097 struct fsm_notify notify;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +010098
99 bool got_port_info;
100 struct mgcp_conn_peer rtp_info;
101 char mgcp_ci_str[MGCP_CONN_ID_LENGTH];
102};
103
104/*! An MGW endpoint with N connections, like "rtpbridge/23@mgw". */
105struct osmo_mgcpc_ep {
106 /*! MGCP client connection to the MGW. */
107 struct mgcp_client *mgcp_client;
108 struct osmo_fsm_inst *fi;
109
110 /*! Endpoint string; at first this might be a wildcard, and upon the first CRCX OK response, this will reflect
111 * the endpoint name returned by the MGW. */
112 char endpoint[MGCP_ENDPOINT_MAXLEN];
113
114 /*! Timeout definitions used for this endpoint, see osmo_mgcpc_ep_fsm_timeouts. */
115 const struct osmo_tdef *T_defs;
116
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200117 /*! True as soon as the first CRCX OK is received. The endpoint name may be determined by the first CRCX
118 * response, so to dispatch any other messages, the FSM instance *must* wait for the first CRCX OK to arrive
119 * first. Once the endpoint name is pinpointed, any amount of operations may be dispatched concurrently. */
120 bool first_crcx_complete;
121
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100122 /*! Endpoint connection slots. Note that each connection has its own set of FSM event numbers to signal success
123 * and failure, depending on its index within this array. See CI_EV_SUCCESS and CI_EV_FAILURE. */
124 struct osmo_mgcpc_ep_ci ci[USABLE_CI];
125};
126
127const struct value_string osmo_mgcp_verb_names[] = {
128 { MGCP_VERB_CRCX, "CRCX" },
129 { MGCP_VERB_MDCX, "MDCX" },
130 { MGCP_VERB_DLCX, "DLCX" },
131 { MGCP_VERB_AUEP, "AUEP" },
132 { MGCP_VERB_RSIP, "RSIP" },
133 {}
134};
135
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200136static void osmo_mgcpc_ep_count(struct osmo_mgcpc_ep *ep, int *occupied, int *pending_not_sent,
137 int *waiting_for_response);
138
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100139static struct osmo_mgcpc_ep_ci *osmo_mgcpc_ep_check_ci(struct osmo_mgcpc_ep_ci *ci)
140{
141 if (!ci)
142 return NULL;
143 if (!ci->ep)
144 return NULL;
145 if (ci < ci->ep->ci || ci >= &ci->ep->ci[USABLE_CI])
146 return NULL;
147 return ci;
148}
149
150static struct osmo_mgcpc_ep_ci *osmo_mgcpc_ep_ci_for_event(struct osmo_mgcpc_ep *ep, uint32_t event)
151{
152 int idx;
153 if (event < FIRST_CI_EVENT)
154 return NULL;
155 idx = EV_TO_CI_IDX(event);
156 if (idx >= sizeof(ep->ci))
157 return NULL;
158 return osmo_mgcpc_ep_check_ci(&ep->ci[idx]);
159}
160
161const char *osmo_mgcpc_ep_name(const struct osmo_mgcpc_ep *ep)
162{
163 if (!ep)
164 return "NULL";
165 if (ep->endpoint[0])
166 return ep->endpoint;
167 return osmo_fsm_inst_name(ep->fi);
168}
169
170const char *mgcp_conn_peer_name(const struct mgcp_conn_peer *info)
171{
172 /* I'd be fine with a smaller buffer and accept truncation, but gcc possibly refuses to build if
173 * this buffer is too small. */
174 static char buf[1024];
175
176 if (!info)
177 return "NULL";
178
179 if (info->endpoint[0]
180 && info->addr[0])
181 snprintf(buf, sizeof(buf), "%s:%s:%u",
182 info->endpoint, info->addr, info->port);
183 else if (info->endpoint[0])
184 snprintf(buf, sizeof(buf), "%s", info->endpoint);
185 else if (info->addr[0])
186 snprintf(buf, sizeof(buf), "%s:%u", info->addr, info->port);
187 else
188 return "empty";
189 return buf;
190}
191
192const char *osmo_mgcpc_ep_ci_name(const struct osmo_mgcpc_ep_ci *ci)
193{
194 const struct mgcp_conn_peer *rtp_info;
195
196 if (!ci)
197 return "NULL";
198
199 rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(ci);
200
201 if (rtp_info)
202 return mgcp_conn_peer_name(rtp_info);
203 return osmo_mgcpc_ep_name(ci->ep);
204}
205
206const char *osmo_mgcpc_ep_ci_id(const struct osmo_mgcpc_ep_ci *ci)
207{
208 if (!ci || !ci->mgcp_ci_str[0])
209 return NULL;
210 return ci->mgcp_ci_str;
211}
212
213static struct value_string osmo_mgcpc_ep_fsm_event_names[33] = {};
214
215static char osmo_mgcpc_ep_fsm_event_name_bufs[32][32] = {};
216
217static void fill_event_names()
218{
219 int i;
220 for (i = 0; i < (ARRAY_SIZE(osmo_mgcpc_ep_fsm_event_names) - 1); i++) {
221 if (i < _OSMO_MGCPC_EP_EV_LAST)
222 continue;
223 if (i < FIRST_CI_EVENT || EV_TO_CI_IDX(i) > USABLE_CI) {
224 osmo_mgcpc_ep_fsm_event_names[i] = (struct value_string){i, "Unused"};
225 continue;
226 }
227 snprintf(osmo_mgcpc_ep_fsm_event_name_bufs[i], sizeof(osmo_mgcpc_ep_fsm_event_name_bufs[i]),
228 "MGW Response for CI #%d", EV_TO_CI_IDX(i));
229 osmo_mgcpc_ep_fsm_event_names[i] = (struct value_string){i, osmo_mgcpc_ep_fsm_event_name_bufs[i]};
230 }
231}
232
233/* T_defs is used to obtain an (Osmocom specific) T2427001: timeout for an MGCP response (note, 2427 corresponds to the
234 * default MGCP port in osmo-mgw). */
235static __attribute__((constructor)) void osmo_mgcpc_ep_fsm_init()
236{
237 OSMO_ASSERT(osmo_fsm_register(&osmo_mgcpc_ep_fsm) == 0);
238 fill_event_names();
239}
240
241struct osmo_mgcpc_ep *osmo_mgcpc_ep_fi_mgwep(struct osmo_fsm_inst *fi)
242{
243 OSMO_ASSERT(fi);
244 OSMO_ASSERT(fi->fsm == &osmo_mgcpc_ep_fsm);
245 OSMO_ASSERT(fi->priv);
246 return fi->priv;
247}
248
249/*! Allocate an osmo_mgcpc_ep FSM.
250 * MGCP messages to set up the endpoint will be sent on the given mgcp_client, as soon as the first
251 * osmo_mgcpc_ep_ci_request() is invoked.
252 *
253 * A typical sequence of events would be:
254 *
255 * ep = osmo_mgcpc_ep_alloc(..., mgcp_client_rtpbridge_wildcard(client));
256 * ci_to_ran = osmo_mgcpc_ep_ci_add(ep);
257 * osmo_mgcpc_ep_ci_request(ci_to_ran, MGCP_VERB_CRCX, verb_info,
258 * my_call_fsm, MY_EVENT_MGCP_OK, MY_EVENT_MGCP_FAIL);
259 * ci_to_cn = osmo_mgcpc_ep_ci_add(ep);
260 * osmo_mgcpc_ep_ci_request(ci_to_cn, MGCP_VERB_CRCX, verb_info,
261 * my_call_fsm, MY_EVENT_MGCP_OK, MY_EVENT_MGCP_FAIL);
262 * ...
263 * osmo_mgcpc_ep_ci_request(ci_to_ran, MGCP_VERB_MDCX, ...);
264 * ...
265 * osmo_mgcpc_ep_clear(ep);
266 * ep = NULL;
267 *
268 * \param parent Parent FSM.
269 * \param parent_term_event Event to dispatch to the parent on termination of this FSM instance.
270 * \param mgcp_client Connection to the MGW.
271 * \param T_defs Timeout definitions to be used for FSM states, see osmo_mgcpc_ep_fsm_timeouts.
272 * \param fsm_id FSM instance ID.
273 * \param endpoint_str_fmt The endpoint string format to send to the MGW upon the first CRCX.
274 * See mgcp_client_rtpbridge_wildcard() for "rtpbridge" endpoints.
275 */
276struct osmo_mgcpc_ep *osmo_mgcpc_ep_alloc(struct osmo_fsm_inst *parent, uint32_t parent_term_event,
277 struct mgcp_client *mgcp_client,
278 const struct osmo_tdef *T_defs,
279 const char *fsm_id,
280 const char *endpoint_str_fmt, ...)
281{
282 va_list ap;
283 struct osmo_fsm_inst *fi;
284 struct osmo_mgcpc_ep *ep;
285 int rc;
286
287 if (!mgcp_client)
288 return NULL;
289
290 fi = osmo_fsm_inst_alloc_child(&osmo_mgcpc_ep_fsm, parent, parent_term_event);
291 OSMO_ASSERT(fi);
292
293 osmo_fsm_inst_update_id(fi, fsm_id);
294
295 ep = talloc_zero(fi, struct osmo_mgcpc_ep);
296 OSMO_ASSERT(ep);
297
298 *ep = (struct osmo_mgcpc_ep){
299 .mgcp_client = mgcp_client,
300 .fi = fi,
301 .T_defs = T_defs,
302 };
303 fi->priv = ep;
304
305 va_start(ap, endpoint_str_fmt);
306 rc = vsnprintf(ep->endpoint, sizeof(ep->endpoint), endpoint_str_fmt ? : "", ap);
307 va_end(ap);
308
309 if (rc <= 0 || rc >= sizeof(ep->endpoint)) {
310 LOG_MGCPC_EP(ep, LOGL_ERROR, "Endpoint name too long or too short: %s\n",
311 ep->endpoint);
312 osmo_fsm_inst_term(ep->fi, OSMO_FSM_TERM_ERROR, 0);
313 return NULL;
314 }
315
316 return ep;
317}
318
319/*! Add a connection to an endpoint.
320 * Allocate a connection identifier slot in the osmo_mgcpc_ep instance, do not yet dispatch a CRCX.
321 * The CRCX is dispatched only upon the first osmo_mgcpc_ep_ci_request().
322 * \param ep Parent endpoint instance.
323 * \param label_fmt Label for logging.
324 */
325struct osmo_mgcpc_ep_ci *osmo_mgcpc_ep_ci_add(struct osmo_mgcpc_ep *ep,
326 const char *label_fmt, ...)
327{
328 va_list ap;
329 int i;
330 struct osmo_mgcpc_ep_ci *ci;
331
332 for (i = 0; i < USABLE_CI; i++) {
333 ci = &ep->ci[i];
334
335 if (ci->occupied || ci->mgcp_client_fi)
336 continue;
337
338 *ci = (struct osmo_mgcpc_ep_ci){
339 .ep = ep,
340 .occupied = true,
341 };
342 if (label_fmt) {
343 va_start(ap, label_fmt);
344 vsnprintf(ci->label, sizeof(ci->label), label_fmt, ap);
345 va_end(ap);
346 }
347 return ci;
348 }
349
350 LOG_MGCPC_EP(ep, LOGL_ERROR,
351 "Cannot allocate another endpoint, all "
352 OSMO_STRINGIFY_VAL(USABLE_CI) " are in use\n");
353
354 return NULL;
355}
356
357static bool osmo_mgcpc_ep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi);
358
359static void on_failure(struct osmo_mgcpc_ep_ci *ci)
360{
Neels Hofmeyrcc0b97e2019-10-01 19:44:10 +0200361 struct osmo_mgcpc_ep *ep = ci->ep;
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100362 struct fsm_notify notify;
Neels Hofmeyrcc0b97e2019-10-01 19:44:10 +0200363 int i;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100364
365 if (!ci->occupied)
366 return;
367
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100368 /* When dispatching an event for this CI, the user may decide to trigger the next request for this conn right
369 * away. So we must be ready with a cleared *ci. Store the notify separately and clear before dispatching. */
370 notify = ci->notify;
371
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100372 *ci = (struct osmo_mgcpc_ep_ci){
373 .ep = ci->ep,
374 };
375
Neels Hofmeyrcc0b97e2019-10-01 19:44:10 +0200376 /* An MGCP failure typically means the endpoint becomes unusable, cancel all pending request (except DLCX).
377 * Particularly, if two CRCX were scheduled and the first fails, we must no longer dispatch the second CRCX. */
378 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
379 struct osmo_mgcpc_ep_ci *other_ci = &ep->ci[i];
380 if (other_ci == ci)
381 continue;
382 if (!other_ci->occupied)
383 continue;
384 if (!other_ci->pending)
385 continue;
386 if (other_ci->sent)
387 continue;
388 if (other_ci->verb == MGCP_VERB_DLCX)
389 continue;
390 /* Just clear the pending request, don't fire more events than below. */
391 other_ci->pending = false;
392 }
393
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100394 /* If this check has terminated the FSM instance, don't fire any more events to prevent use-after-free problems.
395 * The endpoint FSM does dispatch a term event to its parent, and everything should be cleaned like that. */
396 if (!osmo_mgcpc_ep_fsm_check_state_chg_after_response(ci->ep->fi))
397 return;
398
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100399 if (notify.fi)
400 osmo_fsm_inst_dispatch(notify.fi, notify.failure, notify.data);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100401}
402
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200403static int update_endpoint_name(struct osmo_mgcpc_ep_ci *ci, const char *new_endpoint_name)
404{
405 struct osmo_mgcpc_ep *ep = ci->ep;
406 int rc;
407 int i;
408
409 if (!strcmp(ep->endpoint, new_endpoint_name)) {
410 /* Same endpoint name, nothing to do. */
411 return 0;
412 }
413
414 /* The endpoint name should only change on the very first CRCX response. */
415 if (ep->first_crcx_complete) {
416 LOG_CI(ci, LOGL_ERROR, "Reponse returned mismatching endpoint name."
417 " This is endpoint %s, instead received %s\n",
418 ep->endpoint, new_endpoint_name);
419 on_failure(ci);
420 return -EINVAL;
421 }
422
423 /* This is the first CRCX response, update endpoint name. */
424 rc = OSMO_STRLCPY_ARRAY(ep->endpoint, new_endpoint_name);
425 if (rc <= 0 || rc >= sizeof(ep->endpoint)) {
426 LOG_CI(ci, LOGL_ERROR, "Unable to copy endpoint name %s\n", osmo_quote_str(new_endpoint_name, -1));
427 osmo_mgcpc_ep_ci_dlcx(ci);
428 on_failure(ci);
429 return -ENOSPC;
430 }
431
432 /* Make sure already pending requests use this updated endpoint name. */
433 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
434 struct osmo_mgcpc_ep_ci *other_ci = &ep->ci[i];
435 if (!other_ci->occupied)
436 continue;
437 if (!other_ci->pending)
438 continue;
439 if (other_ci->sent)
440 continue;
441 OSMO_STRLCPY_ARRAY(other_ci->verb_info.endpoint, ep->endpoint);
442 }
443 return 0;
444}
445
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100446static void on_success(struct osmo_mgcpc_ep_ci *ci, void *data)
447{
448 struct mgcp_conn_peer *rtp_info;
449
450 if (!ci->occupied)
451 return;
452
453 ci->pending = false;
454
455 switch (ci->verb) {
456 case MGCP_VERB_CRCX:
457 /* If we sent a wildcarded endpoint name on CRCX, we need to store the resulting endpoint
458 * name here. Also, we receive the MGW's RTP port information. */
459 rtp_info = data;
460 OSMO_ASSERT(rtp_info);
461 ci->got_port_info = true;
462 ci->rtp_info = *rtp_info;
463 osmo_strlcpy(ci->mgcp_ci_str, mgcp_conn_get_ci(ci->mgcp_client_fi),
464 sizeof(ci->mgcp_ci_str));
465 if (rtp_info->endpoint[0]) {
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200466 /* On errors, this instance might already be deallocated. Make sure to not access anything after
467 * error. */
468 if (update_endpoint_name(ci, rtp_info->endpoint))
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100469 return;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100470 }
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200471 ci->ep->first_crcx_complete = true;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100472 break;
473
474 default:
475 break;
476 }
477
478 LOG_CI(ci, LOGL_DEBUG, "received successful response to %s: RTP=%s%s\n",
479 osmo_mgcp_verb_name(ci->verb),
480 mgcp_conn_peer_name(ci->got_port_info? &ci->rtp_info : NULL),
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100481 ci->notify.fi ? "" : " (not sending a notification)");
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100482
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100483 if (ci->notify.fi)
484 osmo_fsm_inst_dispatch(ci->notify.fi, ci->notify.success, ci->notify.data);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100485
486 osmo_mgcpc_ep_fsm_check_state_chg_after_response(ci->ep->fi);
487}
488
489/*! Return the MGW's RTP port information for this connection, as returned by the last CRCX/MDCX OK message. */
490const struct mgcp_conn_peer *osmo_mgcpc_ep_ci_get_rtp_info(const struct osmo_mgcpc_ep_ci *ci)
491{
492 ci = osmo_mgcpc_ep_check_ci((struct osmo_mgcpc_ep_ci*)ci);
493 if (!ci)
494 return NULL;
495 if (!ci->got_port_info)
496 return NULL;
497 return &ci->rtp_info;
498}
499
500/*! Return the MGW's RTP port information for this connection, as returned by the last CRCX/MDCX OK message. */
501bool osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(const struct osmo_mgcpc_ep_ci *ci, struct sockaddr_storage *dest)
502{
503 const struct mgcp_conn_peer *rtp_info;
504 struct sockaddr_in *sin;
505
506 rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(ci);
507 if (!rtp_info)
508 return false;
509
510 sin = (struct sockaddr_in *)dest;
511
512 sin->sin_family = AF_INET;
513 sin->sin_addr.s_addr = inet_addr(rtp_info->addr);
514 sin->sin_port = osmo_ntohs(rtp_info->port);
515 return true;
516}
517
Pau Espin Pedrol30907dc2019-05-06 11:54:17 +0200518bool osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(const struct osmo_mgcpc_ep_ci *ci, uint8_t* cid)
519{
520 const struct mgcp_conn_peer *rtp_info;
521
522 rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(ci);
523 if (!rtp_info)
524 return false;
525
526 if (!rtp_info->x_osmo_osmux_use)
527 return false;
528
529 *cid = rtp_info->x_osmo_osmux_cid;
530 return true;
531}
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100532
533static const struct osmo_tdef_state_timeout osmo_mgcpc_ep_fsm_timeouts[32] = {
534 [OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE] = { .T=2427001 },
535};
536
537/* Transition to a state, using the T timer defined in assignment_fsm_timeouts.
538 * The actual timeout value is in turn obtained from osmo_mgcpc_ep.T_defs.
539 * Assumes local variable fi exists. */
540#define osmo_mgcpc_ep_fsm_state_chg(state) \
541 osmo_tdef_fsm_inst_state_chg(fi, state, osmo_mgcpc_ep_fsm_timeouts, \
542 ((struct osmo_mgcpc_ep*)fi->priv)->T_defs, 5)
543
544/*! Dispatch an actual CRCX/MDCX/DLCX message for this connection.
545 * \param ci Connection identifier as obtained from osmo_mgcpc_ep_ci_add().
546 * \param verb MGCP operation to dispatch.
547 * \param verb_info Parameters for the MGCP operation.
548 * \param notify Peer FSM instance to notify of completed/failed operation.
549 * \param event_success Which event to dispatch to 'notify' upon OK response.
550 * \param event_failure Which event to dispatch to 'notify' upon failure response.
551 * \param notify_data Data pointer to pass to the event dispatch for both success and failure.
552 */
553void osmo_mgcpc_ep_ci_request(struct osmo_mgcpc_ep_ci *ci,
554 enum mgcp_verb verb, const struct mgcp_conn_peer *verb_info,
555 struct osmo_fsm_inst *notify,
556 uint32_t event_success, uint32_t event_failure,
557 void *notify_data)
558{
559 struct osmo_mgcpc_ep *ep;
560 struct osmo_fsm_inst *fi;
561 struct osmo_mgcpc_ep_ci cleared_ci;
562 ci = osmo_mgcpc_ep_check_ci(ci);
563
564 if (!ci) {
565 LOGP(DLGLOBAL, LOGL_ERROR, "Invalid MGW endpoint request: no ci\n");
566 goto dispatch_error;
567 }
568 if (!verb_info && verb != MGCP_VERB_DLCX) {
569 LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: missing verb details for %s\n",
570 osmo_mgcp_verb_name(verb));
571 goto dispatch_error;
572 }
573 if ((verb < 0) || (verb > MGCP_VERB_RSIP)) {
574 LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: unknown verb: %s\n",
575 osmo_mgcp_verb_name(verb));
576 goto dispatch_error;
577 }
578
579 ep = ci->ep;
580 fi = ep->fi;
581
582 /* Clear volatile state by explicitly keeping those that should remain. Because we can't assign
583 * the char[] directly, dance through cleared_ci and copy back. */
584 cleared_ci = (struct osmo_mgcpc_ep_ci){
585 .ep = ep,
586 .mgcp_client_fi = ci->mgcp_client_fi,
587 .got_port_info = ci->got_port_info,
588 .rtp_info = ci->rtp_info,
589
590 .occupied = true,
591 /* .pending = true follows below */
592 .verb = verb,
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100593 .notify = {
594 .fi = notify,
595 .success = event_success,
596 .failure = event_failure,
597 .data = notify_data,
598 }
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100599 };
600 osmo_strlcpy(cleared_ci.label, ci->label, sizeof(cleared_ci.label));
601 osmo_strlcpy(cleared_ci.mgcp_ci_str, ci->mgcp_ci_str, sizeof(cleared_ci.mgcp_ci_str));
602 *ci = cleared_ci;
603
Neels Hofmeyr3ff71282019-10-29 17:41:20 +0100604 LOG_CI_VERB(ci, LOGL_DEBUG, "notify=%s\n", osmo_fsm_inst_name(ci->notify.fi));
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100605
606 if (verb_info)
607 ci->verb_info = *verb_info;
608
609 if (ep->endpoint[0]) {
610 if (ci->verb_info.endpoint[0] && strcmp(ci->verb_info.endpoint, ep->endpoint))
611 LOG_CI(ci, LOGL_ERROR,
612 "Warning: Requested %s on endpoint %s, but this CI is on endpoint %s."
613 " Using the proper endpoint instead.\n",
614 osmo_mgcp_verb_name(verb), ci->verb_info.endpoint, ep->endpoint);
615 osmo_strlcpy(ci->verb_info.endpoint, ep->endpoint, sizeof(ci->verb_info.endpoint));
616 }
617
618 switch (ci->verb) {
619 case MGCP_VERB_CRCX:
620 if (ci->mgcp_client_fi) {
621 LOG_CI(ci, LOGL_ERROR, "CRCX can be called only once per MGW endpoint CI\n");
622 on_failure(ci);
623 return;
624 }
625 break;
626
627 case MGCP_VERB_MDCX:
628 if (!ci->mgcp_client_fi) {
629 LOG_CI_VERB(ci, LOGL_ERROR, "The first verb on an unused MGW endpoint CI must be CRCX, not %s\n",
630 osmo_mgcp_verb_name(ci->verb));
631 on_failure(ci);
632 return;
633 }
634 break;
635
636 case MGCP_VERB_DLCX:
637 if (!ci->mgcp_client_fi) {
638 LOG_CI_VERB(ci, LOGL_DEBUG, "Ignoring DLCX on unused MGW endpoint CI\n");
639 return;
640 }
641 break;
642
643 default:
644 LOG_CI(ci, LOGL_ERROR, "This verb is not supported: %s\n", osmo_mgcp_verb_name(ci->verb));
645 on_failure(ci);
646 return;
647 }
648
649 ci->pending = true;
650
651 LOG_CI_VERB(ci, LOGL_DEBUG, "Scheduling\n");
652
653 if (ep->fi->state != OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE)
654 osmo_mgcpc_ep_fsm_state_chg(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE);
655
656 return;
657dispatch_error:
658 if (notify)
659 osmo_fsm_inst_dispatch(notify, event_failure, notify_data);
660}
661
662static int send_verb(struct osmo_mgcpc_ep_ci *ci)
663{
664 int rc;
665 struct osmo_mgcpc_ep *ep = ci->ep;
Neels Hofmeyr055ded72019-10-29 17:46:30 +0100666 struct fsm_notify notify;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100667
668 if (!ci->occupied || !ci->pending || ci->sent)
669 return 0;
670
671 switch (ci->verb) {
672
673 case MGCP_VERB_CRCX:
674 OSMO_ASSERT(!ci->mgcp_client_fi);
675 LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
676 ci->mgcp_client_fi = mgcp_conn_create(ep->mgcp_client, ep->fi,
677 CI_EV_FAILURE(ci), CI_EV_SUCCESS(ci),
678 &ci->verb_info);
679 ci->sent = true;
680 if (!ci->mgcp_client_fi){
681 LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send\n");
682 on_failure(ci);
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200683 return -EINVAL;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100684 }
685 osmo_fsm_inst_update_id(ci->mgcp_client_fi, ci->label);
686 break;
687
688 case MGCP_VERB_MDCX:
689 OSMO_ASSERT(ci->mgcp_client_fi);
690 LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
691 rc = mgcp_conn_modify(ci->mgcp_client_fi, CI_EV_SUCCESS(ci), &ci->verb_info);
692 ci->sent = true;
693 if (rc) {
694 LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send (rc=%d %s)\n", rc, strerror(-rc));
695 on_failure(ci);
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200696 return -EINVAL;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100697 }
698 break;
699
700 case MGCP_VERB_DLCX:
701 LOG_CI(ci, LOGL_DEBUG, "Sending MGCP: %s %s\n",
702 osmo_mgcp_verb_name(ci->verb), ci->mgcp_ci_str);
703 /* The way this is designed, we actually need to forget all about the ci right away. */
704 mgcp_conn_delete(ci->mgcp_client_fi);
Neels Hofmeyr055ded72019-10-29 17:46:30 +0100705 notify = ci->notify;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100706 *ci = (struct osmo_mgcpc_ep_ci){
707 .ep = ep,
708 };
Neels Hofmeyr055ded72019-10-29 17:46:30 +0100709 /* When dispatching an event for this CI, the user may decide to trigger the next request for this conn
710 * right away. So we must be ready with a cleared *ci. */
711 if (notify.fi)
712 osmo_fsm_inst_dispatch(notify.fi, notify.success, notify.data);
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100713 break;
714
715 default:
716 OSMO_ASSERT(false);
717 }
718
719 return 1;
720}
721
722/*! DLCX all connections, terminate the endpoint FSM and free. */
723void osmo_mgcpc_ep_clear(struct osmo_mgcpc_ep *ep)
724{
725 if (!ep)
726 return;
727 osmo_fsm_inst_term(ep->fi, OSMO_FSM_TERM_REGULAR, 0);
728}
729
730static void osmo_mgcpc_ep_count(struct osmo_mgcpc_ep *ep, int *occupied, int *pending_not_sent,
731 int *waiting_for_response)
732{
733 int i;
734
735 if (occupied)
736 *occupied = 0;
737
738 if (pending_not_sent)
739 *pending_not_sent = 0;
740
741 if (waiting_for_response)
742 *waiting_for_response = 0;
743
744 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
745 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
746 if (ci->occupied) {
747 if (occupied)
748 (*occupied)++;
749 } else
750 continue;
751
752 if (ci->pending)
753 LOG_CI_VERB(ci, LOGL_DEBUG, "%s\n",
754 ci->sent ? "waiting for response" : "waiting to be sent");
755 else
756 LOG_CI_VERB(ci, LOGL_DEBUG, "done (%s)\n", mgcp_conn_peer_name(osmo_mgcpc_ep_ci_get_rtp_info(ci)));
757
758 if (ci->pending && ci->sent)
759 if (waiting_for_response)
760 (*waiting_for_response)++;
761 if (ci->pending && !ci->sent)
762 if (pending_not_sent)
763 (*pending_not_sent)++;
764 }
765}
766
767static bool osmo_mgcpc_ep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi)
768{
769 int waiting_for_response;
770 int occupied;
771 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
772
773 osmo_mgcpc_ep_count(ep, &occupied, NULL, &waiting_for_response);
774 LOG_MGCPC_EP(ep, LOGL_DEBUG, "CI in use: %d, waiting for response: %d\n", occupied, waiting_for_response);
775
776 if (!occupied) {
777 /* All CI have been released. The endpoint no longer exists. Notify the parent FSM, by
778 * terminating. */
779 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
780 return false;
781 }
782
783 if (!waiting_for_response) {
784 if (fi->state != OSMO_MGCPC_EP_ST_IN_USE)
785 osmo_mgcpc_ep_fsm_state_chg(OSMO_MGCPC_EP_ST_IN_USE);
786 }
787
788 return true;
789}
790
791static void osmo_mgcpc_ep_fsm_wait_mgw_response_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
792{
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200793 static int re_enter = 0;
794 int rc;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100795 int count = 0;
796 int i;
797 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
798
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200799 re_enter++;
800 OSMO_ASSERT(re_enter < 10);
801
802 /* The first CRCX gives us the endpoint name in the CRCX response. So we must wait for the first CRCX endpoint
803 * response to come in before sending any other MGCP requests -- otherwise we might end up creating new
804 * endpoints instead of acting on the same. This FSM always sends out N requests and waits for all of them to
805 * complete before sending out new requests. Hence we're safe when the very first time at most one request is
806 * sent (which needs to be a CRCX). */
807
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100808 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200809 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
810
811 /* Make sure that only CRCX get dispatched if no CRCX were sent yet. */
812 if (!ep->first_crcx_complete) {
813 if (ci->occupied && ci->verb != MGCP_VERB_CRCX)
814 continue;
815 }
816
817 rc = send_verb(&ep->ci[i]);
818 /* Need to be careful not to access the instance after failure. Event chains may already have
819 * deallocated this memory. */
820 if (rc < 0)
821 return;
822 if (!rc)
823 continue;
824 count++;
825 /* Make sure that we wait for the first CRCX response before dispatching more requests. */
826 if (!ep->first_crcx_complete)
827 break;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100828 }
829
830 LOG_MGCPC_EP(ep, LOGL_DEBUG, "Sent messages: %d\n", count);
Neels Hofmeyrc5479fe2019-04-05 01:36:06 +0200831 if (ep->first_crcx_complete)
832 osmo_mgcpc_ep_fsm_check_state_chg_after_response(fi);
833 re_enter--;
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100834}
835
836static void osmo_mgcpc_ep_fsm_handle_ci_events(struct osmo_fsm_inst *fi, uint32_t event, void *data)
837{
838 struct osmo_mgcpc_ep_ci *ci;
839 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
840 ci = osmo_mgcpc_ep_ci_for_event(ep, event);
841 if (ci) {
842 if (event == CI_EV_SUCCESS(ci))
843 on_success(ci, data);
844 else
845 on_failure(ci);
846 }
847}
848
849static void osmo_mgcpc_ep_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
850{
851 int pending_not_sent;
852 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
853
854 osmo_mgcpc_ep_count(ep, NULL, &pending_not_sent, NULL);
855 if (pending_not_sent)
856 osmo_mgcpc_ep_fsm_state_chg(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE);
857}
858
859#define S(x) (1 << (x))
860
861static const struct osmo_fsm_state osmo_mgcpc_ep_fsm_states[] = {
862 [OSMO_MGCPC_EP_ST_UNUSED] = {
863 .name = "UNUSED",
864 .in_event_mask = 0,
865 .out_state_mask = 0
866 | S(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE)
867 ,
868 },
869 [OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE] = {
870 .name = "WAIT_MGW_RESPONSE",
871 .onenter = osmo_mgcpc_ep_fsm_wait_mgw_response_onenter,
872 .action = osmo_mgcpc_ep_fsm_handle_ci_events,
873 .in_event_mask = 0xffffffff,
874 .out_state_mask = 0
875 | S(OSMO_MGCPC_EP_ST_IN_USE)
876 ,
877 },
878 [OSMO_MGCPC_EP_ST_IN_USE] = {
879 .name = "IN_USE",
880 .onenter = osmo_mgcpc_ep_fsm_in_use_onenter,
881 .action = osmo_mgcpc_ep_fsm_handle_ci_events,
882 .in_event_mask = 0xffffffff, /* mgcp_client_fsm may send parent term anytime */
883 .out_state_mask = 0
884 | S(OSMO_MGCPC_EP_ST_WAIT_MGW_RESPONSE)
885 ,
886 },
887};
888
889static int osmo_mgcpc_ep_fsm_timer_cb(struct osmo_fsm_inst *fi)
890{
891 int i;
892 struct osmo_mgcpc_ep *ep = osmo_mgcpc_ep_fi_mgwep(fi);
893
894 switch (fi->T) {
895 default:
896 for (i = 0; i < ARRAY_SIZE(ep->ci); i++) {
897 struct osmo_mgcpc_ep_ci *ci = &ep->ci[i];
898 if (!ci->occupied)
899 continue;
900 if (!(ci->pending && ci->sent))
901 continue;
902 on_failure(ci);
903 }
904 return 0;
905 }
906
907 return 0;
908}
909
910static struct osmo_fsm osmo_mgcpc_ep_fsm = {
Pau Espin Pedrol182ca3b2019-05-08 14:01:20 +0200911 .name = "mgw-endp",
Neels Hofmeyr538d2c52019-01-28 03:51:35 +0100912 .states = osmo_mgcpc_ep_fsm_states,
913 .num_states = ARRAY_SIZE(osmo_mgcpc_ep_fsm_states),
914 .log_subsys = DLMGCP,
915 .event_names = osmo_mgcpc_ep_fsm_event_names,
916 .timer_cb = osmo_mgcpc_ep_fsm_timer_cb,
917 /* The FSM termination will automatically trigger any mgcp_client_fsm instances to DLCX. */
918};