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