blob: 7fc28f1a0aaa64a1edc78ba0c52cd39c6e09fbb6 [file] [log] [blame]
Harald Welte3d60dbd2019-03-08 15:10:30 +01001/* IPA keep-alive FSM; Periodically transmit IPA_PING and expect IPA_PONG in return.
2 *
3 * (C) 2019 by Harald Welte <laforge@gnumonks.org>
4 *
5 * All Rights Reserved
6 *
7 * SPDX-License-Identifier: GPL-2.0+
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
Harald Welte3d60dbd2019-03-08 15:10:30 +010019 */
20
21#include <osmocom/core/fsm.h>
22#include <osmocom/core/timer.h>
23#include <osmocom/core/msgb.h>
24#include <osmocom/core/logging.h>
25
26#include <osmocom/gsm/protocol/ipaccess.h>
27
28#include <osmocom/abis/ipa.h>
29
30#define S(x) (1 << (x))
31
32
33/* generate a msgb containing an IPA CCM PING message */
34static struct msgb *gen_ipa_ping(void)
35{
36 struct msgb *msg = msgb_alloc_headroom(64, 32, "IPA PING");
37 if (!msg)
38 return NULL;
39
40 msgb_put_u8(msg, IPAC_MSGT_PING);
41 ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS);
42
43 return msg;
44}
45
46enum osmo_ipa_keepalive_state {
47 OSMO_IPA_KA_S_INIT,
48 OSMO_IPA_KA_S_IDLE, /* waiting for next interval */
49 OSMO_IPA_KA_S_WAIT_RESP, /* waiting for response to keepalive */
50};
51
52enum osmo_ipa_keepalive_event {
53 OSMO_IPA_KA_E_START,
54 OSMO_IPA_KA_E_STOP,
55 OSMO_IPA_KA_E_PONG,
56};
57
58static const struct value_string ipa_keepalive_event_names[] = {
59 OSMO_VALUE_STRING(OSMO_IPA_KA_E_START),
60 OSMO_VALUE_STRING(OSMO_IPA_KA_E_STOP),
61 OSMO_VALUE_STRING(OSMO_IPA_KA_E_PONG),
62 { 0, NULL }
63};
64
65enum ipa_fsm_timer {
66 T_SEND_NEXT_PING = 1,
67 T_PONG_NOT_RECEIVED = 2,
68};
69
70struct ipa_fsm_priv {
71 struct ipa_keepalive_params params;
72
73 struct ipa_server_conn *srv_conn;
74 struct ipa_client_conn *client_conn;
Eric Wild51b61002019-07-09 13:48:06 +020075 void *generic;
Harald Welte3d60dbd2019-03-08 15:10:30 +010076 ipa_keepalive_timeout_cb_t *timeout_cb;
Eric Wild51b61002019-07-09 13:48:06 +020077 ipa_keepalive_send_cb_t *send_fn;
Harald Welte3d60dbd2019-03-08 15:10:30 +010078};
79
80static void ipa_ka_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
81{
82 struct ipa_fsm_priv *ifp = fi->priv;
83
84 switch (event) {
85 case OSMO_IPA_KA_E_START:
86 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP,
87 ifp->params.wait_for_resp, T_PONG_NOT_RECEIVED);
88 break;
89 default:
90 OSMO_ASSERT(0);
91 break;
92 }
93}
94
95static void ipa_ka_wait_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
96{
97 struct ipa_fsm_priv *ifp = fi->priv;
98 struct msgb *msg;
99
100 /* Send an IPA PING to the peer */
101 msg = gen_ipa_ping();
102 OSMO_ASSERT(msg);
103
Eric Wild51b61002019-07-09 13:48:06 +0200104 if (ifp->send_fn && ifp->generic) {
105 ifp->send_fn(fi, ifp->generic, msg);
106 return;
107 }
108
109 if (ifp->srv_conn) {
110 if (ifp->send_fn)
111 ifp->send_fn(fi, ifp->srv_conn, msg);
112 else
113 ipa_server_conn_send(ifp->srv_conn, msg);
114 }
Harald Welte3d60dbd2019-03-08 15:10:30 +0100115 else {
116 OSMO_ASSERT(ifp->client_conn);
Eric Wild51b61002019-07-09 13:48:06 +0200117 if (ifp->send_fn)
118 ifp->send_fn(fi, ifp->client_conn, msg);
119 else
120 ipa_client_conn_send(ifp->client_conn, msg);
Harald Welte3d60dbd2019-03-08 15:10:30 +0100121 }
122}
123
124static void ipa_ka_wait_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
125{
126 struct ipa_fsm_priv *ifp = fi->priv;
127
128 switch (event) {
129 case OSMO_IPA_KA_E_PONG:
130 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_IDLE,
131 ifp->params.interval, T_SEND_NEXT_PING);
132 break;
133 default:
134 OSMO_ASSERT(0);
135 }
136}
137
138static int ipa_ka_fsm_timer_cb(struct osmo_fsm_inst *fi)
139{
140 struct ipa_fsm_priv *ifp = fi->priv;
141 void *conn;
142
143 switch (fi->T) {
144 case T_SEND_NEXT_PING:
145 /* send another PING */
146 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP,
147 ifp->params.wait_for_resp, T_PONG_NOT_RECEIVED);
148 return 0;
149 case T_PONG_NOT_RECEIVED:
150 /* PONG not received within time */
151 if (ifp->srv_conn)
152 conn = ifp->srv_conn;
Eric Wild51b61002019-07-09 13:48:06 +0200153 else if (ifp->client_conn)
Harald Welte3d60dbd2019-03-08 15:10:30 +0100154 conn = ifp->client_conn;
Eric Wild51b61002019-07-09 13:48:06 +0200155 else
156 conn = ifp->generic;
Harald Welte3d60dbd2019-03-08 15:10:30 +0100157 if (ifp->timeout_cb)
Eric Wild51b61002019-07-09 13:48:06 +0200158 return ifp->timeout_cb(fi, conn);
Harald Welte3d60dbd2019-03-08 15:10:30 +0100159 /* ask fsm core to terminate us */
160 return 1;
161 default:
162 OSMO_ASSERT(0);
163 }
164}
165
166static void ipa_ka_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
167{
168 switch (event) {
169 case OSMO_IPA_KA_E_STOP:
170 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_INIT, 0, 0);
171 break;
172 default:
173 OSMO_ASSERT(0);
174 break;
175 }
176}
177
178static const struct osmo_fsm_state ipa_keepalive_states[] = {
179 [OSMO_IPA_KA_S_INIT] = {
180 .name = "INIT",
181 .in_event_mask = S(OSMO_IPA_KA_E_START),
Harald Welte3e03bc22019-04-06 20:26:25 +0200182 .out_state_mask = S(OSMO_IPA_KA_S_WAIT_RESP) | S(OSMO_IPA_KA_S_INIT),
Harald Welte3d60dbd2019-03-08 15:10:30 +0100183 .action = ipa_ka_init,
184 },
185 [OSMO_IPA_KA_S_IDLE] = {
186 .name = "IDLE",
187 .out_state_mask = S(OSMO_IPA_KA_S_WAIT_RESP) | S(OSMO_IPA_KA_S_INIT),
188 /* no permitted events aside from E_START, which is handled in allstate_events */
189 },
190 [OSMO_IPA_KA_S_WAIT_RESP] = {
191 .name = "WAIT_RESP",
192 .in_event_mask = S(OSMO_IPA_KA_E_PONG),
193 .out_state_mask = S(OSMO_IPA_KA_S_IDLE) | S(OSMO_IPA_KA_S_INIT),
194 .action = ipa_ka_wait_resp,
195 .onenter = ipa_ka_wait_resp_onenter,
196 },
197};
198
199static struct osmo_fsm ipa_keepalive_fsm = {
200 .name = "IPA-KEEPALIVE",
201 .states = ipa_keepalive_states,
202 .num_states = ARRAY_SIZE(ipa_keepalive_states),
203 .log_subsys = DLINP,
Harald Welte02c5e9d2019-04-06 20:22:43 +0200204 .allstate_event_mask = S(OSMO_IPA_KA_E_STOP),
Harald Welte3d60dbd2019-03-08 15:10:30 +0100205 .allstate_action = ipa_ka_allstate_action,
206 .event_names = ipa_keepalive_event_names,
207 .timer_cb = ipa_ka_fsm_timer_cb,
208};
209
210static __attribute__((constructor)) void on_dso_load(void)
211{
Vadim Yanitskiy4c2c0882020-01-01 13:59:15 +0100212 OSMO_ASSERT(osmo_fsm_register(&ipa_keepalive_fsm) == 0);
Harald Welte3d60dbd2019-03-08 15:10:30 +0100213}
214
215
216static struct osmo_fsm_inst *
217__ipa_conn_alloc_keepalive_fsm(void *ctx, const struct ipa_keepalive_params *params, const char *id)
218{
219 struct osmo_fsm_inst *fi;
220 struct ipa_fsm_priv *ifp;
221
222 fi = osmo_fsm_inst_alloc(&ipa_keepalive_fsm, ctx, NULL, LOGL_DEBUG, id);
223 if (!fi)
224 return NULL;
225 ifp = talloc_zero(fi, struct ipa_fsm_priv);
226 if (!ifp) {
227 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
228 return NULL;
229 }
230 memcpy(&ifp->params, params, sizeof(ifp->params));
231 fi->priv = ifp;
232
233 return fi;
234}
235
236/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG.
237 * \param[in] client The client connection for which to crate the FSM. Used as talloc context.
238 * \param[in] params Parameters describing the keepalive FSM time-outs.
239 * \param[in] id String used as identifier for the FSM.
240 * \returns pointer to the newly-created FSM instance; NULL in case of error. */
241struct osmo_fsm_inst *ipa_client_conn_alloc_keepalive_fsm(struct ipa_client_conn *client,
242 const struct ipa_keepalive_params *params,
243 const char *id)
244{
245 struct osmo_fsm_inst *fi;
246 struct ipa_fsm_priv *ifp;
247
248 fi = __ipa_conn_alloc_keepalive_fsm(client, params, id);
249 if (!fi)
250 return NULL;
251 ifp = fi->priv;
252 ifp->client_conn = client;
253 return fi;
254}
255
256/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG.
257 * \param[in] server The server connection for which to crate the FSM. Used as talloc context.
258 * \param[in] params Parameters describing the keepalive FSM time-outs.
259 * \param[in] id String used as identifier for the FSM.
260 * \returns pointer to the newly-created FSM instance; NULL in case of error. */
261struct osmo_fsm_inst *ipa_server_conn_alloc_keepalive_fsm(struct ipa_server_conn *server,
262 const struct ipa_keepalive_params *params,
263 const char *id)
264{
265 struct osmo_fsm_inst *fi;
266 struct ipa_fsm_priv *ifp;
267
268 fi = __ipa_conn_alloc_keepalive_fsm(server, params, id);
269 if (!fi)
270 return NULL;
271 ifp = fi->priv;
272 ifp->srv_conn = server;
273 return fi;
274}
275
Eric Wild51b61002019-07-09 13:48:06 +0200276/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG.
277 * \param[in] ctx Talloc context.
278 * \param[in] data Data to pass to write/timeout cb.
279 * \param[in] params Parameters describing the keepalive FSM time-outs.
280 * \param[in] id String used as identifier for the FSM.
281 * \returns pointer to the newly-created FSM instance; NULL in case of error. */
282struct osmo_fsm_inst *ipa_generic_conn_alloc_keepalive_fsm(void *ctx, void* data,
283 const struct ipa_keepalive_params *params,
284 const char *id)
285{
286 struct osmo_fsm_inst *fi;
287 struct ipa_fsm_priv *ifp;
288
289 fi = __ipa_conn_alloc_keepalive_fsm(ctx, params, id);
290 if (!fi)
291 return NULL;
292 ifp = fi->priv;
293 ifp->generic = data;
294 return fi;
295}
296
Harald Welte3d60dbd2019-03-08 15:10:30 +0100297/*! Set a timeout call-back which is to be called once the peer doesn't respond anymore */
298void ipa_keepalive_fsm_set_timeout_cb(struct osmo_fsm_inst *fi, ipa_keepalive_timeout_cb_t *cb)
299{
300 struct ipa_fsm_priv *ifp = fi->priv;
301 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
302 ifp->timeout_cb = cb;
303}
304
Eric Wild51b61002019-07-09 13:48:06 +0200305/*! Set a custom send callback for sending pings */
306void ipa_keepalive_fsm_set_send_cb(struct osmo_fsm_inst *fi, ipa_keepalive_send_cb_t *fn)
307{
308 struct ipa_fsm_priv *ifp = fi->priv;
309 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
310 ifp->send_fn = fn;
311}
312
Harald Welte3d60dbd2019-03-08 15:10:30 +0100313/*! Inform IPA Keepalive FSM that a PONG has been received. */
314void ipa_keepalive_fsm_pong_received(struct osmo_fsm_inst *fi)
315{
316 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
317 osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_PONG, NULL);
318}
319
320/*! Start the ping/pong procedure of the IPA Keepalive FSM. */
321void ipa_keepalive_fsm_start(struct osmo_fsm_inst *fi)
322{
323 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
324 osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_START, NULL);
325}
326
327/*! Stop the ping/pong procedure of the IPA Keepalive FSM. */
328void ipa_keepalive_fsm_stop(struct osmo_fsm_inst *fi)
329{
330 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
331 osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_STOP, NULL);
332}