blob: a2565e71fb57c0f9f0a3d7758d99fdad355fc071 [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
80enum fsm_bsc_mgcp_states {
81 ST_CRCX,
82 ST_CRCX_RESP,
83 ST_READY,
84 ST_MDCX_RESP,
85 ST_DLCX_RESP,
86};
87
88enum bsc_mgcp_fsm_evt {
89 EV_CRCX,
90 EV_CRCX_RESP,
91 EV_MDCX,
92 EV_MDCX_RESP,
93 EV_DLCX,
94 EV_DLCX_RESP,
95};
96
97static struct msgb *make_crcx_msg_bind(struct mgcp_ctx *mgcp_ctx)
98{
99 struct mgcp_msg mgcp_msg;
100
101 mgcp_msg = (struct mgcp_msg) {
102 .verb = MGCP_VERB_CRCX,
103 .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE),
104 .call_id = mgcp_ctx->conn_peer_local.call_id,
105 .conn_mode = MGCP_CONN_LOOPBACK,
106 };
107 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN);
108
109 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
110}
111
112static struct msgb *make_crcx_msg_bind_connect(struct mgcp_ctx *mgcp_ctx)
113{
114 struct mgcp_msg mgcp_msg;
115
116 mgcp_msg = (struct mgcp_msg) {
117 .verb = MGCP_VERB_CRCX,.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |
118 MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |
119 MGCP_MSG_PRESENCE_AUDIO_PORT),
120 .call_id = mgcp_ctx->conn_peer_local.call_id,
121 .conn_mode = MGCP_CONN_RECV_SEND,
122 .audio_ip = mgcp_ctx->conn_peer_local.addr,
123 .audio_port = mgcp_ctx->conn_peer_local.port,
124 };
125 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN);
126
127 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
128}
129
130static struct msgb *make_mdcx_msg(struct mgcp_ctx *mgcp_ctx)
131{
132 struct mgcp_msg mgcp_msg;
133
134 mgcp_msg = (struct mgcp_msg) {
135 .verb = MGCP_VERB_MDCX,
136 .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
137 MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
138 .call_id = mgcp_ctx->conn_peer_remote.call_id,
139 .conn_id = mgcp_ctx->conn_id,
140 .conn_mode = MGCP_CONN_RECV_SEND,
141 .audio_ip = mgcp_ctx->conn_peer_local.addr,
142 .audio_port = mgcp_ctx->conn_peer_local.port,
143 };
144 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN);
145
146 /* Note: We take the endpoint and the call_id from the remote
147 * connection info, because we can be confident that the
148 * information there is valid. For the local info, we explicitly
149 * allow endpoint and call_id to be optional */
150 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
151}
152
153struct msgb *make_dlcx_msg(struct mgcp_ctx *mgcp_ctx)
154{
155 struct mgcp_msg mgcp_msg;
156
157 mgcp_msg = (struct mgcp_msg) {
158 .verb = MGCP_VERB_DLCX,
159 .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID),
160 .call_id = mgcp_ctx->conn_peer_remote.call_id,
161 .conn_id = mgcp_ctx->conn_id,
162 };
163 osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN);
164
165 return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);
166}
167
168static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv);
169
170static void fsm_crcx_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
171{
172 struct mgcp_ctx *mgcp_ctx = data;
173 struct mgcp_client *mgcp;
174 struct msgb *msg;
175 int rc;
176
177 OSMO_ASSERT(mgcp_ctx);
178 mgcp = mgcp_ctx->mgcp;
179 OSMO_ASSERT(mgcp);
180
181 switch (event) {
182 case EV_CRCX:
183 LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: creating connection on MGW endpoint:%s...\n",
184 mgcp_ctx->conn_peer_local.endpoint);
185
186 if (mgcp_ctx->conn_peer_local.port)
187 msg = make_crcx_msg_bind_connect(mgcp_ctx);
188 else
189 msg = make_crcx_msg_bind(mgcp_ctx);
190 OSMO_ASSERT(msg);
191
192 mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
193 mgcp_ctx->mgw_trans_pending = true;
194 rc = mgcp_client_tx(mgcp, msg, mgw_crcx_resp_cb, fi);
195 if (rc < 0) {
196 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
197 return;
198 }
199
200 osmo_fsm_inst_state_chg(fi, ST_CRCX_RESP, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
201 break;
202 default:
203 OSMO_ASSERT(false);
204 break;
205 }
206}
207
208static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv)
209{
210 struct osmo_fsm_inst *fi = priv;
211 struct mgcp_ctx *mgcp_ctx;
212 int rc;
213
214 OSMO_ASSERT(fi);
215 mgcp_ctx = fi->priv;
216 OSMO_ASSERT(mgcp_ctx);
217
218 mgcp_ctx->mgw_trans_pending = false;
219
220 if (r->head.response_code != 200) {
221 LOGPFSML(fi, LOGL_ERROR,
222 "MGW/CRCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
223 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
224 return;
225 }
226
227 osmo_strlcpy(mgcp_ctx->conn_id, r->head.conn_id, sizeof(mgcp_ctx->conn_id));
228 LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded with CI: %s\n", mgcp_ctx->conn_id);
229
230 rc = mgcp_response_parse_params(r);
231 if (rc) {
232 LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: Cannot parse CRCX response\n");
233 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
234 return;
235 }
236 LOGPFSML(fi, LOGL_DEBUG, "MGW/CRCX: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
237
238 osmo_strlcpy(mgcp_ctx->conn_peer_remote.addr, r->audio_ip, sizeof(mgcp_ctx->conn_peer_remote.addr));
239 mgcp_ctx->conn_peer_remote.port = r->audio_port;
240
241 if (strlen(r->head.endpoint) > 0) {
242 /* If we get an endpoint identifier back from the MGW, take it */
243 osmo_strlcpy(mgcp_ctx->conn_peer_remote.endpoint, r->head.endpoint,
244 sizeof(mgcp_ctx->conn_peer_remote.endpoint));
245 } else if (strstr(mgcp_ctx->conn_peer_local.endpoint, "*") == NULL) {
246 /* If we do not get an endpoint identifier back and the
247 * identifier we used to create the connection is not a
248 * wildcarded one, we take the local endpoint identifier
249 * instead */
250 osmo_strlcpy(mgcp_ctx->conn_peer_remote.endpoint, mgcp_ctx->conn_peer_local.endpoint,
251 sizeof(mgcp_ctx->conn_peer_local.endpoint));
252 } else {
253 LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: CRCX yielded not suitable endpoint identifier\n");
254 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
255 return;
256 }
257
258 mgcp_ctx->conn_peer_remote.call_id = mgcp_ctx->conn_peer_local.call_id;
259
260 osmo_fsm_inst_dispatch(fi, EV_CRCX_RESP, mgcp_ctx);
261}
262
263static void fsm_crcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
264{
265 struct mgcp_ctx *mgcp_ctx = data;
266 OSMO_ASSERT(mgcp_ctx);
267
268 switch (event) {
269 case EV_CRCX_RESP:
270 osmo_fsm_inst_state_chg(fi, ST_READY, 0, 0);
271 if (mgcp_ctx->terminate) {
272 /* Trigger immediate DLCX if DLCX was requested while the FSM was
273 * busy with the previous operation */
274 LOGPFSML(fi, LOGL_ERROR, "MGW/CRCX: FSM was busy while DLCX was requested, executing now...\n");
275 osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx);
276 } else
277 osmo_fsm_inst_dispatch(fi->proc.parent, mgcp_ctx->parent_evt, &mgcp_ctx->conn_peer_remote);
278 break;
279 default:
280 OSMO_ASSERT(false);
281 break;
282 }
283}
284
285static void mgw_mdcx_resp_cb(struct mgcp_response *r, void *priv);
286static void mgw_dlcx_resp_cb(struct mgcp_response *r, void *priv);
287
288static void fsm_ready_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
289{
290 struct mgcp_ctx *mgcp_ctx = data;
291 struct msgb *msg;
292 struct mgcp_client *mgcp;
293 uint32_t new_state;
294 int rc;
295
296 OSMO_ASSERT(mgcp_ctx);
297 mgcp = mgcp_ctx->mgcp;
298 OSMO_ASSERT(mgcp);
299
300 switch (event) {
301 case EV_MDCX:
302 msg = make_mdcx_msg(mgcp_ctx);
303 OSMO_ASSERT(msg);
304 rc = mgcp_client_tx(mgcp, msg, mgw_mdcx_resp_cb, fi);
305 new_state = ST_MDCX_RESP;
306 break;
307 case EV_DLCX:
308 msg = make_dlcx_msg(mgcp_ctx);
309 OSMO_ASSERT(msg);
310 rc = mgcp_client_tx(mgcp, msg, mgw_dlcx_resp_cb, fi);
311 new_state = ST_DLCX_RESP;
312 break;
313 default:
314 OSMO_ASSERT(false);
315 break;
316 }
317
318 mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
319 mgcp_ctx->mgw_trans_pending = true;
320
321 if (rc < 0) {
322 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
323 return;
324 }
325
326 osmo_fsm_inst_state_chg(fi, new_state, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
327}
328
329static void mgw_mdcx_resp_cb(struct mgcp_response *r, void *priv)
330{
331 struct osmo_fsm_inst *fi = priv;
332 struct mgcp_ctx *mgcp_ctx;
333 int rc;
334
335 OSMO_ASSERT(fi);
336 mgcp_ctx = fi->priv;
337 OSMO_ASSERT(mgcp_ctx);
338
339 mgcp_ctx->mgw_trans_pending = false;
340
341 if (r->head.response_code != 200) {
342 LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: response yields error: %d %s\n", r->head.response_code,
343 r->head.comment);
344 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
345 return;
346 }
347
348 rc = mgcp_response_parse_params(r);
349 if (rc) {
350 LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: Cannot parse MDCX response\n");
351 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
352 return;
353 }
354 LOGPFSML(fi, LOGL_DEBUG, "MGW/MDCX: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
355
356 osmo_strlcpy(mgcp_ctx->conn_peer_remote.addr, r->audio_ip, sizeof(mgcp_ctx->conn_peer_remote.addr));
357 mgcp_ctx->conn_peer_remote.port = r->audio_port;
358
359 osmo_fsm_inst_dispatch(fi, EV_MDCX_RESP, mgcp_ctx);
360}
361
362static void fsm_mdcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
363{
364 struct mgcp_ctx *mgcp_ctx = data;
365 OSMO_ASSERT(mgcp_ctx);
366
367 switch (event) {
368 case EV_MDCX_RESP:
369 osmo_fsm_inst_state_chg(fi, ST_READY, 0, 0);
370 if (mgcp_ctx->terminate) {
371 /* Trigger immediate DLCX if DLCX was requested while the FSM was
372 * busy with the previous operation */
373 LOGPFSML(fi, LOGL_ERROR, "MGW/MDCX: FSM was busy while DLCX was requested, executing now...\n");
374 osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx);
375 } else
376 osmo_fsm_inst_dispatch(fi->proc.parent, mgcp_ctx->parent_evt, &mgcp_ctx->conn_peer_remote);
377 break;
378 default:
379 OSMO_ASSERT(false);
380 break;
381 }
382}
383
384static void mgw_dlcx_resp_cb(struct mgcp_response *r, void *priv)
385{
386 struct osmo_fsm_inst *fi = priv;
387 struct mgcp_ctx *mgcp_ctx;
388
389 OSMO_ASSERT(fi);
390 mgcp_ctx = fi->priv;
391 OSMO_ASSERT(mgcp_ctx);
392
393 mgcp_ctx->mgw_trans_pending = false;
394
395 if (r->head.response_code != 250) {
396 LOGPFSML(fi, LOGL_ERROR,
397 "MGW/DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
398 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
399 return;
400 }
401
402 osmo_fsm_inst_dispatch(fi, EV_DLCX_RESP, mgcp_ctx);
403}
404
405static void fsm_dlcx_resp_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
406{
407 struct mgcp_ctx *mgcp_ctx = data;
408 OSMO_ASSERT(mgcp_ctx);
409
410 switch (event) {
411 case EV_DLCX_RESP:
412 /* Rub out the connection identifier, since the connection
413 * is no longer present and we will use the connection id
414 * to know in error cases if the connection is still present
415 * or not */
416 memset(mgcp_ctx->conn_id, 0, sizeof(mgcp_ctx->conn_id));
417
418 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
419 break;
420 default:
421 OSMO_ASSERT(false);
422 break;
423 }
424}
425
426static int fsm_timeout_cb(struct osmo_fsm_inst *fi)
427{
428 struct mgcp_ctx *mgcp_ctx = fi->priv;
429 struct mgcp_client *mgcp;
430
431 OSMO_ASSERT(mgcp_ctx);
432 mgcp = mgcp_ctx->mgcp;
433 OSMO_ASSERT(mgcp);
434
435 if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) {
436 /* Note: We were unable to communicate with the MGW,
437 * unfortunately there is no meaningful action we can take
438 * now other than giving up. */
439 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
440 } else {
441 /* Note: Ther must not be any unsolicited timers
442 * in this FSM. If so, we have serious problem. */
443 OSMO_ASSERT(false);
444 }
445
446 return 0;
447}
448
449static void fsm_cleanup_cb(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
450{
451 struct mgcp_ctx *mgcp_ctx = fi->priv;
452 struct mgcp_client *mgcp;
453 struct msgb *msg;
454
455 OSMO_ASSERT(mgcp_ctx);
456 mgcp = mgcp_ctx->mgcp;
457 OSMO_ASSERT(mgcp);
458
459 /* If there is still a transaction pending, cancel it now. */
460 if (mgcp_ctx->mgw_trans_pending)
461 mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans);
462
463 /* Should the FSM be terminated while there are still open connections
464 * on the MGW, we send an unconditional DLCX to terminate the
465 * connection. This is not the normal case. The user should always use
466 * mgcp_conn_delete() to instruct the FSM to perform a graceful exit */
467 if (strlen(mgcp_ctx->conn_id)) {
468 LOGPFSML(fi, LOGL_ERROR,
469 "MGW/DLCX: aprupt FSM termination with connections still present, sending unconditional DLCX...\n");
470 msg = make_dlcx_msg(mgcp_ctx);
471 OSMO_ASSERT(msg);
472 mgcp_client_tx(mgcp, msg, NULL, NULL);
473 }
474
475 talloc_free(mgcp_ctx);
476}
477
478static struct osmo_fsm_state fsm_mgcp_client_states[] = {
479
480 /* Initial CRCX state. This state is immediately entered and executed
481 * when the FSM is started. The rationale is that we first have to
482 * create a connectin before we can execute other operations on that
483 * connection. */
484 [ST_CRCX] = {
485 .in_event_mask = S(EV_CRCX),
486 .out_state_mask = S(ST_CRCX_RESP),
487 .name = OSMO_STRINGIFY(ST_CRCX),
488 .action = fsm_crcx_cb,
489 },
490
491 /* Wait for the response to a CRCX operation, check and process the
492 * results, change to ST_READY afterwards. */
493 [ST_CRCX_RESP] = {
494 .in_event_mask = S(EV_CRCX_RESP),
495 .out_state_mask = S(ST_READY),
496 .name = OSMO_STRINGIFY(ST_CRCX_RESP),
497 .action = fsm_crcx_resp_cb,
498 },
499
500 /* In this idle state we wait for further operations (e.g. MDCX) that
501 * can be executed by the user using the API. There is no timeout in
502 * this state. The connection lives on until the user decides to
503 * terminate it (DLCX). */
504 [ST_READY] = {
505 .in_event_mask = S(EV_MDCX) | S(EV_DLCX),
506 .out_state_mask = S(ST_MDCX_RESP) | S(ST_DLCX_RESP),
507 .name = OSMO_STRINGIFY(ST_READY),
508 .action = fsm_ready_cb,
509 },
510
511 /* Wait for the response of a MDCX operation, check and process the
512 * results, change to ST_READY afterwards. */
513 [ST_MDCX_RESP] = {
514 .in_event_mask = S(EV_MDCX_RESP),
515 .out_state_mask = S(ST_READY),
516 .name = OSMO_STRINGIFY(ST_MDCX_RESP),
517 .action = fsm_mdcx_resp_cb,
518 },
519
520 /* Wait for the response of a DLCX operation and terminate the FSM
521 * normally. */
522 [ST_DLCX_RESP] = {
523 .in_event_mask = S(EV_DLCX_RESP),
524 .out_state_mask = 0,
525 .name = OSMO_STRINGIFY(ST_DLCX_RESP),
526 .action = fsm_dlcx_resp_cb,
527 },
528};
529
530static struct osmo_fsm fsm_mgcp_client = {
531 .name = "MGCP_CONN",
532 .states = fsm_mgcp_client_states,
533 .num_states = ARRAY_SIZE(fsm_mgcp_client_states),
534 .timer_cb = fsm_timeout_cb,
535 .cleanup = fsm_cleanup_cb,
536};
537
538/*! allocate FSM, and create a new connection on the MGW.
539 * \param[in] mgcp MGCP client descriptor.
540 * \param[in] mgcpparent_fi Parent FSM instance.
541 * \param[in] parent_term_evt Event to be sent to parent when terminating.
542 * \param[in] parent_evt Event to be sent to parent when operation is done.
543 * \param[in] conn_peer Connection parameters (ip, port...).
544 * \returns newly-allocated, initialized and registered FSM instance, NULL on error. */
545struct osmo_fsm_inst *mgcp_conn_create(struct mgcp_client *mgcp, struct osmo_fsm_inst *parent_fi,
546 uint32_t parent_term_evt, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer)
547{
548 struct mgcp_ctx *mgcp_ctx;
549 static bool fsm_registered = false;
550 struct osmo_fsm_inst *fi;
551 struct in_addr ip_test;
552
553 OSMO_ASSERT(parent_fi);
554 OSMO_ASSERT(mgcp);
555 OSMO_ASSERT(conn_peer);
556
557 /* Check if IP/Port informstaion in conn info makes sense */
558 if (conn_peer->port && inet_aton(conn_peer->addr, &ip_test) == 0)
559 return NULL;
560
561 /* Register the fsm description (if not already done) */
562 if (fsm_registered == false) {
563 osmo_fsm_register(&fsm_mgcp_client);
564 fsm_registered = true;
565 }
566
567 /* Allocate and configure a new fsm instance */
568 fi = osmo_fsm_inst_alloc_child(&fsm_mgcp_client, parent_fi, parent_term_evt);
569 OSMO_ASSERT(fi);
570 mgcp_ctx = talloc_zero(fi, struct mgcp_ctx);
571 OSMO_ASSERT(mgcp_ctx);
572 mgcp_ctx->mgcp = mgcp;
573 mgcp_ctx->parent_evt = parent_evt;
574
575 memcpy(&mgcp_ctx->conn_peer_local, conn_peer, sizeof(mgcp_ctx->conn_peer_local));
576 fi->priv = mgcp_ctx;
577
578 /* start state machine */
579 OSMO_ASSERT(fi->state == ST_CRCX);
580 osmo_fsm_inst_dispatch(fi, EV_CRCX, mgcp_ctx);
581
582 return fi;
583}
584
585/*! modify an existing connection on the MGW.
586 * \param[in] fi FSM instance.
587 * \param[in] parent_evt Event to be sent to parent when operation is done.
588 * \param[in] conn_peer New connection information (ip, port...).
589 * \returns 0 on success, -EINVAL on error. */
590int mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer)
591{
592 OSMO_ASSERT(fi);
593 struct mgcp_ctx *mgcp_ctx = fi->priv;
594 struct in_addr ip_test;
595
596 OSMO_ASSERT(mgcp_ctx);
597 OSMO_ASSERT(conn_peer);
598
599 /* The user must not issue an MDCX before the CRCX has completed,
600 * if this happens, it means that the parent FSM has overhead the
601 * parent_evt (mandatory!) and executed the MDCX without even
602 * waiting for the results. Another reason could be that the
603 * parent FSM got messed up */
604 OSMO_ASSERT(fi->state != ST_CRCX_RESP);
605
606 /* If the user tries to issue an MDCX while an DLCX operation is
607 * pending, there must be a serious problem with the paren FSM.
608 * Eeither the parent_term_evt (mandatory!) has been overheard,
609 * or the parant FSM got messed so badly that it still assumes
610 * a live connection although it as killed it. */
611 OSMO_ASSERT(fi->state != ST_DLCX_RESP);
612
613 /* Check if IP/Port parameters make sense */
614 if (conn_peer->port == 0)
615 return -EINVAL;
616 if (inet_aton(conn_peer->addr, &ip_test) == 0)
617 return -EINVAL;
618
619 /*! The user may supply an endpoint identifier in conn_peer. The
620 * identifier is then checked. This check is optional. Later steps do
621 * not depend on the endpoint identifier supplied here because it is
622 * already implicitly known from the CRCX phase. */
623 if (strlen(conn_peer->endpoint) && strcmp(conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint))
624 return -EINVAL;
625
626 /*! Note: The call-id is implicitly known from the previous CRCX and
627 * will not be checked even when it is set in conn_peer. */
628
629 mgcp_ctx->parent_evt = parent_evt;
630 memcpy(&mgcp_ctx->conn_peer_local, conn_peer, sizeof(mgcp_ctx->conn_peer_local));
631 osmo_fsm_inst_dispatch(fi, EV_MDCX, mgcp_ctx);
632 return 0;
633}
634
635/*! delete existing connection on the MGW, destroy FSM afterwards.
636 * \param[in] fi FSM instance. */
637void mgcp_conn_delete(struct osmo_fsm_inst *fi)
638{
639 OSMO_ASSERT(fi);
640 struct mgcp_ctx *mgcp_ctx = fi->priv;
641
642 OSMO_ASSERT(mgcp_ctx);
643
644 /* Unlink FSM from parent */
645 osmo_fsm_inst_unlink_parent(fi, NULL);
646
647 /* An error situation where the parent FSM must be killed immediately
648 * may lead into a situation where the DLCX can not be executed right
649 * at that moment because the FSM is still busy with another operation.
650 * In those cases we postpone the DLCX so that the FSM and the
651 * connections on the MGW get cleaned up gracefully. */
652 if (fi->state != ST_READY) {
653 LOGPFSML(fi, LOGL_ERROR, "MGW: operation still pending, DLCX will be postponed.\n");
654 mgcp_ctx->terminate = true;
655 return;
656 }
657 osmo_fsm_inst_dispatch(fi, EV_DLCX, mgcp_ctx);
658}