blob: 6e1e7b81d384061461f8fdd9bccdf2c1a349995f [file] [log] [blame]
Philipp Maier8bda7a72018-01-17 14:32:23 +01001/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
2 * All Rights Reserved
3 *
4 * Author: Philipp Maier
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21#include <osmocom/mgcp_client/mgcp_client.h>
22#include <osmocom/mgcp_client/mgcp_client_fsm.h>
23#include <osmocom/core/utils.h>
24#include <osmocom/core/fsm.h>
25#include <osmocom/core/byteswap.h>
26#include <arpa/inet.h>
27#include <osmocom/core/logging.h>
28
29/* Context information, this is attached to the priv pointer of the FSM and
30 * is also handed back when dispatcheing events to the parent FSM. This is
31 * purly intened and not meant to be accessible for the API user */
32struct mgcp_ctx {
33 /* MGCP client instance that is used to interact with the MGW */
34 struct mgcp_client *mgcp;
35
36 /* The ID of the last pending transaction. This is used internally
37 * to cancel the transaction in case of an error */
38 mgcp_trans_id_t mgw_pending_trans;
39
40 /* Flag to mark that there is a pending transaction */
41 bool mgw_trans_pending;
42
43 /* Connection ID which has been assigned by he MGW */
44 char conn_id[MGCP_CONN_ID_LENGTH];
45
46 /* Local RTP connection info, the MGW will send outgoing traffic to the
47 * ip/port specified here. The Address does not have to be choosen right
48 * on the creation of a connection. It can always be modified later by
49 * the user. */
50 struct mgcp_conn_peer conn_peer_local;
51
52 /* Remote RTP connection info, the ip/port specified here is the address
53 * where the MGW expects the RTP data to be sent. This address is
54 * defined by soly by the MGW and can not be influenced by the user. */
55 struct mgcp_conn_peer conn_peer_remote;
56
57 /* The terminate flag is a way to handle cornercase sitations that
58 * might occur when the user runs into an error situation and sends
59 * a DLCX command while the FSM is waiting for a response. In this
60 * case the DLCX command is not executed immediately. Instead the
61 * terminate flag is set. When the response to from the previous
62 * operation is received, we know that there is a DLCX event is
63 * pending. The FSM then generates the EV_DLCX by itsself before
64 * it enters ST_READY to cause the immediate execution of the
65 * DLCX procedure. (If normal operations are executed too fast,
66 * the API functions will return an error. In general, the user
67 * should synchronize using the callback events) */
68 bool terminate;
69
70 /* Event that is sent when the current operation is completed (except
71 * for DLCX, there the specified parent_term_evt is sent instead) */
72 uint32_t parent_evt;
73};
74
75#define S(x) (1 << (x))
76
77#define MGCP_MGW_TIMEOUT 4 /* in seconds */
78#define MGCP_MGW_TIMEOUT_TIMER_NR 1
79
Philipp Maier01f03952018-02-26 14:33:25 +010080enum fsm_mgcp_client_states {
Philipp Maier8bda7a72018-01-17 14:32:23 +010081 ST_CRCX,
82 ST_CRCX_RESP,
83 ST_READY,
84 ST_MDCX_RESP,
85 ST_DLCX_RESP,
86};
87
Philipp Maier01f03952018-02-26 14:33:25 +010088enum fsm_mgcp_client_evt {
Philipp Maier8bda7a72018-01-17 14:32:23 +010089 EV_CRCX,
90 EV_CRCX_RESP,
91 EV_MDCX,
92 EV_MDCX_RESP,
93 EV_DLCX,
94 EV_DLCX_RESP,
95};
96
Philipp Maierd2e3a522018-02-26 14:29:01 +010097static const struct value_string fsm_mgcp_client_evt_names[] = {
98 OSMO_VALUE_STRING(EV_CRCX),
99 OSMO_VALUE_STRING(EV_CRCX_RESP),
100 OSMO_VALUE_STRING(EV_MDCX),
101 OSMO_VALUE_STRING(EV_MDCX_RESP),
102 OSMO_VALUE_STRING(EV_DLCX),
103 OSMO_VALUE_STRING(EV_DLCX_RESP),
104 {0, NULL}
105};
106
Philipp Maier8bda7a72018-01-17 14:32:23 +0100107static struct msgb *make_crcx_msg_bind(struct mgcp_ctx *mgcp_ctx)
108{
109 struct mgcp_msg mgcp_msg;
110
111 mgcp_msg = (struct mgcp_msg) {
112 .verb = MGCP_VERB_CRCX,
113 .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE),
114 .call_id = mgcp_ctx->conn_peer_local.call_id,
115 .conn_mode = MGCP_CONN_LOOPBACK,
116 };
117 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN);
118
119 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
120}
121
122static struct msgb *make_crcx_msg_bind_connect(struct mgcp_ctx *mgcp_ctx)
123{
124 struct mgcp_msg mgcp_msg;
125
126 mgcp_msg = (struct mgcp_msg) {
127 .verb = MGCP_VERB_CRCX,.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |
128 MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |
129 MGCP_MSG_PRESENCE_AUDIO_PORT),
130 .call_id = mgcp_ctx->conn_peer_local.call_id,
131 .conn_mode = MGCP_CONN_RECV_SEND,
132 .audio_ip = mgcp_ctx->conn_peer_local.addr,
133 .audio_port = mgcp_ctx->conn_peer_local.port,
134 };
135 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN);
136
137 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
138}
139
140static struct msgb *make_mdcx_msg(struct mgcp_ctx *mgcp_ctx)
141{
142 struct mgcp_msg mgcp_msg;
143
144 mgcp_msg = (struct mgcp_msg) {
145 .verb = MGCP_VERB_MDCX,
146 .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
147 MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
148 .call_id = mgcp_ctx->conn_peer_remote.call_id,
149 .conn_id = mgcp_ctx->conn_id,
150 .conn_mode = MGCP_CONN_RECV_SEND,
151 .audio_ip = mgcp_ctx->conn_peer_local.addr,
152 .audio_port = mgcp_ctx->conn_peer_local.port,
153 };
154 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN);
155
156 /* Note: We take the endpoint and the call_id from the remote
157 * connection info, because we can be confident that the
158 * information there is valid. For the local info, we explicitly
159 * allow endpoint and call_id to be optional */
160 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
161}
162
163struct msgb *make_dlcx_msg(struct mgcp_ctx *mgcp_ctx)
164{
165 struct mgcp_msg mgcp_msg;
166
167 mgcp_msg = (struct mgcp_msg) {
168 .verb = MGCP_VERB_DLCX,
169 .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID),
170 .call_id = mgcp_ctx->conn_peer_remote.call_id,
171 .conn_id = mgcp_ctx->conn_id,
172 };
173 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN);
174
175 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
176}
177
178static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv);
179
180static void fsm_crcx_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
181{
182 struct mgcp_ctx *mgcp_ctx = data;
183 struct mgcp_client *mgcp;
184 struct msgb *msg;
185 int rc;
186
187 OSMO_ASSERT(mgcp_ctx);
188 mgcp = mgcp_ctx->mgcp;
189 OSMO_ASSERT(mgcp);
190
191 switch (event) {
192 case EV_CRCX:
193 LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: creating connection on MGW endpoint:%s...\n",
194 mgcp_ctx->conn_peer_local.endpoint);
195
196 if (mgcp_ctx->conn_peer_local.port)
197 msg = make_crcx_msg_bind_connect(mgcp_ctx);
198 else
199 msg = make_crcx_msg_bind(mgcp_ctx);
200 OSMO_ASSERT(msg);
201
202 mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
203 mgcp_ctx->mgw_trans_pending = true;
204 rc = mgcp_client_tx(mgcp, msg, mgw_crcx_resp_cb, fi);
205 if (rc < 0) {
206 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
207 return;
208 }
209
210 osmo_fsm_inst_state_chg(fi, ST_CRCX_RESP, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
211 break;
212 default:
213 OSMO_ASSERT(false);
214 break;
215 }
216}
217
218static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv)
219{
220 struct osmo_fsm_inst *fi = priv;
221 struct mgcp_ctx *mgcp_ctx;
222 int rc;
223
224 OSMO_ASSERT(fi);
225 mgcp_ctx = fi->priv;
226 OSMO_ASSERT(mgcp_ctx);
227
228 mgcp_ctx->mgw_trans_pending = false;
229
230 if (r->head.response_code != 200) {
231 LOGPFSML(fi, LOGL_ERROR,
232 "MGW/CRCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
233 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
234 return;
235 }
236
237 osmo_strlcpy(mgcp_ctx->conn_id, r->head.conn_id, sizeof(mgcp_ctx->conn_id));
238 LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded with CI: %s\n", mgcp_ctx->conn_id);
239
240 rc = mgcp_response_parse_params(r);
241 if (rc) {
242 LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: Cannot parse CRCX response\n");
243 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
244 return;
245 }
246 LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
247
248 osmo_strlcpy(mgcp_ctx->conn_peer_remote.addr, r->audio_ip, sizeof(mgcp_ctx->conn_peer_remote.addr));
249 mgcp_ctx->conn_peer_remote.port = r->audio_port;
250
251 if (strlen(r->head.endpoint) > 0) {
252 /* If we get an endpoint identifier back from the MGW, take it */
253 osmo_strlcpy(mgcp_ctx->conn_peer_remote.endpoint, r->head.endpoint,
254 sizeof(mgcp_ctx->conn_peer_remote.endpoint));
255 } else if (strstr(mgcp_ctx->conn_peer_local.endpoint, "*") == NULL) {
256 /* If we do not get an endpoint identifier back and the
257 * identifier we used to create the connection is not a
258 * wildcarded one, we take the local endpoint identifier
259 * instead */
260 osmo_strlcpy(mgcp_ctx->conn_peer_remote.endpoint, mgcp_ctx->conn_peer_local.endpoint,
261 sizeof(mgcp_ctx->conn_peer_local.endpoint));
262 } else {
263 LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: CRCX yielded not suitable endpoint identifier\n");
264 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
265 return;
266 }
267
268 mgcp_ctx->conn_peer_remote.call_id = mgcp_ctx->conn_peer_local.call_id;
269
270 osmo_fsm_inst_dispatch(fi, EV_CRCX_RESP, mgcp_ctx);
271}
272
273static void fsm_crcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
274{
275 struct mgcp_ctx *mgcp_ctx = data;
276 OSMO_ASSERT(mgcp_ctx);
277
278 switch (event) {
279 case EV_CRCX_RESP:
280 osmo_fsm_inst_state_chg(fi, ST_READY, 0, 0);
281 if (mgcp_ctx->terminate) {
282 /* Trigger immediate DLCX if DLCX was requested while the FSM was
283 * busy with the previous operation */
284 LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: FSM was busy while DLCX was requested, executing now...\n");
285 osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx);
286 } else
287 osmo_fsm_inst_dispatch(fi->proc.parent, mgcp_ctx->parent_evt, &mgcp_ctx->conn_peer_remote);
288 break;
289 default:
290 OSMO_ASSERT(false);
291 break;
292 }
293}
294
295static void mgw_mdcx_resp_cb(struct mgcp_response *r, void *priv);
296static void mgw_dlcx_resp_cb(struct mgcp_response *r, void *priv);
297
298static void fsm_ready_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
299{
300 struct mgcp_ctx *mgcp_ctx = data;
301 struct msgb *msg;
302 struct mgcp_client *mgcp;
303 uint32_t new_state;
304 int rc;
305
306 OSMO_ASSERT(mgcp_ctx);
307 mgcp = mgcp_ctx->mgcp;
308 OSMO_ASSERT(mgcp);
309
310 switch (event) {
311 case EV_MDCX:
312 msg = make_mdcx_msg(mgcp_ctx);
313 OSMO_ASSERT(msg);
314 rc = mgcp_client_tx(mgcp, msg, mgw_mdcx_resp_cb, fi);
315 new_state = ST_MDCX_RESP;
316 break;
317 case EV_DLCX:
318 msg = make_dlcx_msg(mgcp_ctx);
319 OSMO_ASSERT(msg);
320 rc = mgcp_client_tx(mgcp, msg, mgw_dlcx_resp_cb, fi);
321 new_state = ST_DLCX_RESP;
322 break;
323 default:
324 OSMO_ASSERT(false);
325 break;
326 }
327
328 mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
329 mgcp_ctx->mgw_trans_pending = true;
330
331 if (rc < 0) {
332 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
333 return;
334 }
335
336 osmo_fsm_inst_state_chg(fi, new_state, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
337}
338
339static void mgw_mdcx_resp_cb(struct mgcp_response *r, void *priv)
340{
341 struct osmo_fsm_inst *fi = priv;
342 struct mgcp_ctx *mgcp_ctx;
343 int rc;
344
345 OSMO_ASSERT(fi);
346 mgcp_ctx = fi->priv;
347 OSMO_ASSERT(mgcp_ctx);
348
349 mgcp_ctx->mgw_trans_pending = false;
350
351 if (r->head.response_code != 200) {
352 LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: response yields error: %d %s\n", r->head.response_code,
353 r->head.comment);
354 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
355 return;
356 }
357
358 rc = mgcp_response_parse_params(r);
359 if (rc) {
360 LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: Cannot parse MDCX response\n");
361 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
362 return;
363 }
364 LOGPFSML(fi, LOGL_DEBUG, "MGW/MDCX: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
365
366 osmo_strlcpy(mgcp_ctx->conn_peer_remote.addr, r->audio_ip, sizeof(mgcp_ctx->conn_peer_remote.addr));
367 mgcp_ctx->conn_peer_remote.port = r->audio_port;
368
369 osmo_fsm_inst_dispatch(fi, EV_MDCX_RESP, mgcp_ctx);
370}
371
372static void fsm_mdcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
373{
374 struct mgcp_ctx *mgcp_ctx = data;
375 OSMO_ASSERT(mgcp_ctx);
376
377 switch (event) {
378 case EV_MDCX_RESP:
379 osmo_fsm_inst_state_chg(fi, ST_READY, 0, 0);
380 if (mgcp_ctx->terminate) {
381 /* Trigger immediate DLCX if DLCX was requested while the FSM was
382 * busy with the previous operation */
383 LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: FSM was busy while DLCX was requested, executing now...\n");
384 osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx);
385 } else
386 osmo_fsm_inst_dispatch(fi->proc.parent, mgcp_ctx->parent_evt, &mgcp_ctx->conn_peer_remote);
387 break;
388 default:
389 OSMO_ASSERT(false);
390 break;
391 }
392}
393
394static void mgw_dlcx_resp_cb(struct mgcp_response *r, void *priv)
395{
396 struct osmo_fsm_inst *fi = priv;
397 struct mgcp_ctx *mgcp_ctx;
398
399 OSMO_ASSERT(fi);
400 mgcp_ctx = fi->priv;
401 OSMO_ASSERT(mgcp_ctx);
402
403 mgcp_ctx->mgw_trans_pending = false;
404
405 if (r->head.response_code != 250) {
406 LOGPFSML(fi, LOGL_ERROR,
407 "MGW/DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
408 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
409 return;
410 }
411
412 osmo_fsm_inst_dispatch(fi, EV_DLCX_RESP, mgcp_ctx);
413}
414
415static void fsm_dlcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
416{
417 struct mgcp_ctx *mgcp_ctx = data;
418 OSMO_ASSERT(mgcp_ctx);
419
420 switch (event) {
421 case EV_DLCX_RESP:
422 /* Rub out the connection identifier, since the connection
423 * is no longer present and we will use the connection id
424 * to know in error cases if the connection is still present
425 * or not */
426 memset(mgcp_ctx->conn_id, 0, sizeof(mgcp_ctx->conn_id));
427
428 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
429 break;
430 default:
431 OSMO_ASSERT(false);
432 break;
433 }
434}
435
436static int fsm_timeout_cb(struct osmo_fsm_inst *fi)
437{
438 struct mgcp_ctx *mgcp_ctx = fi->priv;
439 struct mgcp_client *mgcp;
440
441 OSMO_ASSERT(mgcp_ctx);
442 mgcp = mgcp_ctx->mgcp;
443 OSMO_ASSERT(mgcp);
444
445 if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) {
446 /* Note: We were unable to communicate with the MGW,
447 * unfortunately there is no meaningful action we can take
448 * now other than giving up. */
449 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
450 } else {
451 /* Note: Ther must not be any unsolicited timers
452 * in this FSM. If so, we have serious problem. */
453 OSMO_ASSERT(false);
454 }
455
456 return 0;
457}
458
459static void fsm_cleanup_cb(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
460{
461 struct mgcp_ctx *mgcp_ctx = fi->priv;
462 struct mgcp_client *mgcp;
463 struct msgb *msg;
464
465 OSMO_ASSERT(mgcp_ctx);
466 mgcp = mgcp_ctx->mgcp;
467 OSMO_ASSERT(mgcp);
468
469 /* If there is still a transaction pending, cancel it now. */
470 if (mgcp_ctx->mgw_trans_pending)
471 mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans);
472
473 /* Should the FSM be terminated while there are still open connections
474 * on the MGW, we send an unconditional DLCX to terminate the
475 * connection. This is not the normal case. The user should always use
476 * mgcp_conn_delete() to instruct the FSM to perform a graceful exit */
477 if (strlen(mgcp_ctx->conn_id)) {
478 LOGPFSML(fi, LOGL_ERROR,
479 "MGW/DLCX: aprupt FSM termination with connections still present, sending unconditional DLCX...\n");
480 msg = make_dlcx_msg(mgcp_ctx);
481 OSMO_ASSERT(msg);
482 mgcp_client_tx(mgcp, msg, NULL, NULL);
483 }
484
485 talloc_free(mgcp_ctx);
486}
487
488static struct osmo_fsm_state fsm_mgcp_client_states[] = {
489
490 /* Initial CRCX state. This state is immediately entered and executed
491 * when the FSM is started. The rationale is that we first have to
492 * create a connectin before we can execute other operations on that
493 * connection. */
494 [ST_CRCX] = {
495 .in_event_mask = S(EV_CRCX),
496 .out_state_mask = S(ST_CRCX_RESP),
497 .name = OSMO_STRINGIFY(ST_CRCX),
498 .action = fsm_crcx_cb,
499 },
500
501 /* Wait for the response to a CRCX operation, check and process the
502 * results, change to ST_READY afterwards. */
503 [ST_CRCX_RESP] = {
504 .in_event_mask = S(EV_CRCX_RESP),
505 .out_state_mask = S(ST_READY),
506 .name = OSMO_STRINGIFY(ST_CRCX_RESP),
507 .action = fsm_crcx_resp_cb,
508 },
509
510 /* In this idle state we wait for further operations (e.g. MDCX) that
511 * can be executed by the user using the API. There is no timeout in
512 * this state. The connection lives on until the user decides to
513 * terminate it (DLCX). */
514 [ST_READY] = {
515 .in_event_mask = S(EV_MDCX) | S(EV_DLCX),
516 .out_state_mask = S(ST_MDCX_RESP) | S(ST_DLCX_RESP),
517 .name = OSMO_STRINGIFY(ST_READY),
518 .action = fsm_ready_cb,
519 },
520
521 /* Wait for the response of a MDCX operation, check and process the
522 * results, change to ST_READY afterwards. */
523 [ST_MDCX_RESP] = {
524 .in_event_mask = S(EV_MDCX_RESP),
525 .out_state_mask = S(ST_READY),
526 .name = OSMO_STRINGIFY(ST_MDCX_RESP),
527 .action = fsm_mdcx_resp_cb,
528 },
529
530 /* Wait for the response of a DLCX operation and terminate the FSM
531 * normally. */
532 [ST_DLCX_RESP] = {
533 .in_event_mask = S(EV_DLCX_RESP),
534 .out_state_mask = 0,
535 .name = OSMO_STRINGIFY(ST_DLCX_RESP),
536 .action = fsm_dlcx_resp_cb,
537 },
538};
539
540static struct osmo_fsm fsm_mgcp_client = {
541 .name = "MGCP_CONN",
542 .states = fsm_mgcp_client_states,
543 .num_states = ARRAY_SIZE(fsm_mgcp_client_states),
544 .timer_cb = fsm_timeout_cb,
545 .cleanup = fsm_cleanup_cb,
Philipp Maierd2e3a522018-02-26 14:29:01 +0100546 .event_names = fsm_mgcp_client_evt_names,
Philipp Maier8bda7a72018-01-17 14:32:23 +0100547};
548
549/*! allocate FSM, and create a new connection on the MGW.
550 * \param[in] mgcp MGCP client descriptor.
Neels Hofmeyred1cff52018-05-17 23:59:46 +0200551 * \param[in] parent_fi Parent FSM instance.
Philipp Maier8bda7a72018-01-17 14:32:23 +0100552 * \param[in] parent_term_evt Event to be sent to parent when terminating.
553 * \param[in] parent_evt Event to be sent to parent when operation is done.
554 * \param[in] conn_peer Connection parameters (ip, port...).
555 * \returns newly-allocated, initialized and registered FSM instance, NULL on error. */
556struct osmo_fsm_inst *mgcp_conn_create(struct mgcp_client *mgcp, struct osmo_fsm_inst *parent_fi,
557 uint32_t parent_term_evt, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer)
558{
559 struct mgcp_ctx *mgcp_ctx;
560 static bool fsm_registered = false;
561 struct osmo_fsm_inst *fi;
562 struct in_addr ip_test;
563
564 OSMO_ASSERT(parent_fi);
565 OSMO_ASSERT(mgcp);
566 OSMO_ASSERT(conn_peer);
567
568 /* Check if IP/Port informstaion in conn info makes sense */
569 if (conn_peer->port && inet_aton(conn_peer->addr, &ip_test) == 0)
570 return NULL;
571
572 /* Register the fsm description (if not already done) */
573 if (fsm_registered == false) {
574 osmo_fsm_register(&fsm_mgcp_client);
575 fsm_registered = true;
576 }
577
578 /* Allocate and configure a new fsm instance */
579 fi = osmo_fsm_inst_alloc_child(&fsm_mgcp_client, parent_fi, parent_term_evt);
580 OSMO_ASSERT(fi);
581 mgcp_ctx = talloc_zero(fi, struct mgcp_ctx);
582 OSMO_ASSERT(mgcp_ctx);
583 mgcp_ctx->mgcp = mgcp;
584 mgcp_ctx->parent_evt = parent_evt;
585
586 memcpy(&mgcp_ctx->conn_peer_local, conn_peer, sizeof(mgcp_ctx->conn_peer_local));
587 fi->priv = mgcp_ctx;
588
589 /* start state machine */
590 OSMO_ASSERT(fi->state == ST_CRCX);
591 osmo_fsm_inst_dispatch(fi, EV_CRCX, mgcp_ctx);
592
593 return fi;
594}
595
596/*! modify an existing connection on the MGW.
597 * \param[in] fi FSM instance.
598 * \param[in] parent_evt Event to be sent to parent when operation is done.
599 * \param[in] conn_peer New connection information (ip, port...).
600 * \returns 0 on success, -EINVAL on error. */
601int mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer)
602{
603 OSMO_ASSERT(fi);
604 struct mgcp_ctx *mgcp_ctx = fi->priv;
605 struct in_addr ip_test;
606
607 OSMO_ASSERT(mgcp_ctx);
608 OSMO_ASSERT(conn_peer);
609
610 /* The user must not issue an MDCX before the CRCX has completed,
611 * if this happens, it means that the parent FSM has overhead the
612 * parent_evt (mandatory!) and executed the MDCX without even
613 * waiting for the results. Another reason could be that the
614 * parent FSM got messed up */
615 OSMO_ASSERT(fi->state != ST_CRCX_RESP);
616
617 /* If the user tries to issue an MDCX while an DLCX operation is
618 * pending, there must be a serious problem with the paren FSM.
619 * Eeither the parent_term_evt (mandatory!) has been overheard,
620 * or the parant FSM got messed so badly that it still assumes
621 * a live connection although it as killed it. */
622 OSMO_ASSERT(fi->state != ST_DLCX_RESP);
623
624 /* Check if IP/Port parameters make sense */
625 if (conn_peer->port == 0)
626 return -EINVAL;
627 if (inet_aton(conn_peer->addr, &ip_test) == 0)
628 return -EINVAL;
629
630 /*! The user may supply an endpoint identifier in conn_peer. The
631 * identifier is then checked. This check is optional. Later steps do
632 * not depend on the endpoint identifier supplied here because it is
633 * already implicitly known from the CRCX phase. */
634 if (strlen(conn_peer->endpoint) && strcmp(conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint))
635 return -EINVAL;
636
637 /*! Note: The call-id is implicitly known from the previous CRCX and
638 * will not be checked even when it is set in conn_peer. */
639
640 mgcp_ctx->parent_evt = parent_evt;
641 memcpy(&mgcp_ctx->conn_peer_local, conn_peer, sizeof(mgcp_ctx->conn_peer_local));
642 osmo_fsm_inst_dispatch(fi, EV_MDCX, mgcp_ctx);
643 return 0;
644}
645
646/*! delete existing connection on the MGW, destroy FSM afterwards.
647 * \param[in] fi FSM instance. */
648void mgcp_conn_delete(struct osmo_fsm_inst *fi)
649{
650 OSMO_ASSERT(fi);
651 struct mgcp_ctx *mgcp_ctx = fi->priv;
652
653 OSMO_ASSERT(mgcp_ctx);
654
655 /* Unlink FSM from parent */
656 osmo_fsm_inst_unlink_parent(fi, NULL);
657
658 /* An error situation where the parent FSM must be killed immediately
659 * may lead into a situation where the DLCX can not be executed right
660 * at that moment because the FSM is still busy with another operation.
661 * In those cases we postpone the DLCX so that the FSM and the
662 * connections on the MGW get cleaned up gracefully. */
663 if (fi->state != ST_READY) {
664 LOGPFSML(fi, LOGL_ERROR, "MGW: operation still pending, DLCX will be postponed.\n");
665 mgcp_ctx->terminate = true;
666 return;
667 }
668 osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx);
669}