blob: 172a36f2783264567b557a7de2b04079754959fe [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 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 *
23 */
24
25#include <osmocom/core/fsm.h>
26#include <osmocom/core/timer.h>
27#include <osmocom/core/msgb.h>
28#include <osmocom/core/logging.h>
29
30#include <osmocom/gsm/protocol/ipaccess.h>
31
32#include <osmocom/abis/ipa.h>
33
34#define S(x) (1 << (x))
35
36
37/* generate a msgb containing an IPA CCM PING message */
38static struct msgb *gen_ipa_ping(void)
39{
40 struct msgb *msg = msgb_alloc_headroom(64, 32, "IPA PING");
41 if (!msg)
42 return NULL;
43
44 msgb_put_u8(msg, IPAC_MSGT_PING);
45 ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS);
46
47 return msg;
48}
49
50enum osmo_ipa_keepalive_state {
51 OSMO_IPA_KA_S_INIT,
52 OSMO_IPA_KA_S_IDLE, /* waiting for next interval */
53 OSMO_IPA_KA_S_WAIT_RESP, /* waiting for response to keepalive */
54};
55
56enum osmo_ipa_keepalive_event {
57 OSMO_IPA_KA_E_START,
58 OSMO_IPA_KA_E_STOP,
59 OSMO_IPA_KA_E_PONG,
60};
61
62static const struct value_string ipa_keepalive_event_names[] = {
63 OSMO_VALUE_STRING(OSMO_IPA_KA_E_START),
64 OSMO_VALUE_STRING(OSMO_IPA_KA_E_STOP),
65 OSMO_VALUE_STRING(OSMO_IPA_KA_E_PONG),
66 { 0, NULL }
67};
68
69enum ipa_fsm_timer {
70 T_SEND_NEXT_PING = 1,
71 T_PONG_NOT_RECEIVED = 2,
72};
73
74struct ipa_fsm_priv {
75 struct ipa_keepalive_params params;
76
77 struct ipa_server_conn *srv_conn;
78 struct ipa_client_conn *client_conn;
Eric Wild51b61002019-07-09 13:48:06 +020079 void *generic;
Harald Welte3d60dbd2019-03-08 15:10:30 +010080 ipa_keepalive_timeout_cb_t *timeout_cb;
Eric Wild51b61002019-07-09 13:48:06 +020081 ipa_keepalive_send_cb_t *send_fn;
Harald Welte3d60dbd2019-03-08 15:10:30 +010082};
83
84static void ipa_ka_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
85{
86 struct ipa_fsm_priv *ifp = fi->priv;
87
88 switch (event) {
89 case OSMO_IPA_KA_E_START:
90 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP,
91 ifp->params.wait_for_resp, T_PONG_NOT_RECEIVED);
92 break;
93 default:
94 OSMO_ASSERT(0);
95 break;
96 }
97}
98
99static void ipa_ka_wait_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
100{
101 struct ipa_fsm_priv *ifp = fi->priv;
102 struct msgb *msg;
103
104 /* Send an IPA PING to the peer */
105 msg = gen_ipa_ping();
106 OSMO_ASSERT(msg);
107
Eric Wild51b61002019-07-09 13:48:06 +0200108 if (ifp->send_fn && ifp->generic) {
109 ifp->send_fn(fi, ifp->generic, msg);
110 return;
111 }
112
113 if (ifp->srv_conn) {
114 if (ifp->send_fn)
115 ifp->send_fn(fi, ifp->srv_conn, msg);
116 else
117 ipa_server_conn_send(ifp->srv_conn, msg);
118 }
Harald Welte3d60dbd2019-03-08 15:10:30 +0100119 else {
120 OSMO_ASSERT(ifp->client_conn);
Eric Wild51b61002019-07-09 13:48:06 +0200121 if (ifp->send_fn)
122 ifp->send_fn(fi, ifp->client_conn, msg);
123 else
124 ipa_client_conn_send(ifp->client_conn, msg);
Harald Welte3d60dbd2019-03-08 15:10:30 +0100125 }
126}
127
128static void ipa_ka_wait_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
129{
130 struct ipa_fsm_priv *ifp = fi->priv;
131
132 switch (event) {
133 case OSMO_IPA_KA_E_PONG:
134 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_IDLE,
135 ifp->params.interval, T_SEND_NEXT_PING);
136 break;
137 default:
138 OSMO_ASSERT(0);
139 }
140}
141
142static int ipa_ka_fsm_timer_cb(struct osmo_fsm_inst *fi)
143{
144 struct ipa_fsm_priv *ifp = fi->priv;
145 void *conn;
146
147 switch (fi->T) {
148 case T_SEND_NEXT_PING:
149 /* send another PING */
150 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP,
151 ifp->params.wait_for_resp, T_PONG_NOT_RECEIVED);
152 return 0;
153 case T_PONG_NOT_RECEIVED:
154 /* PONG not received within time */
155 if (ifp->srv_conn)
156 conn = ifp->srv_conn;
Eric Wild51b61002019-07-09 13:48:06 +0200157 else if (ifp->client_conn)
Harald Welte3d60dbd2019-03-08 15:10:30 +0100158 conn = ifp->client_conn;
Eric Wild51b61002019-07-09 13:48:06 +0200159 else
160 conn = ifp->generic;
Harald Welte3d60dbd2019-03-08 15:10:30 +0100161 if (ifp->timeout_cb)
Eric Wild51b61002019-07-09 13:48:06 +0200162 return ifp->timeout_cb(fi, conn);
Harald Welte3d60dbd2019-03-08 15:10:30 +0100163 /* ask fsm core to terminate us */
164 return 1;
165 default:
166 OSMO_ASSERT(0);
167 }
168}
169
170static void ipa_ka_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
171{
172 switch (event) {
173 case OSMO_IPA_KA_E_STOP:
174 osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_INIT, 0, 0);
175 break;
176 default:
177 OSMO_ASSERT(0);
178 break;
179 }
180}
181
182static const struct osmo_fsm_state ipa_keepalive_states[] = {
183 [OSMO_IPA_KA_S_INIT] = {
184 .name = "INIT",
185 .in_event_mask = S(OSMO_IPA_KA_E_START),
Harald Welte3e03bc22019-04-06 20:26:25 +0200186 .out_state_mask = S(OSMO_IPA_KA_S_WAIT_RESP) | S(OSMO_IPA_KA_S_INIT),
Harald Welte3d60dbd2019-03-08 15:10:30 +0100187 .action = ipa_ka_init,
188 },
189 [OSMO_IPA_KA_S_IDLE] = {
190 .name = "IDLE",
191 .out_state_mask = S(OSMO_IPA_KA_S_WAIT_RESP) | S(OSMO_IPA_KA_S_INIT),
192 /* no permitted events aside from E_START, which is handled in allstate_events */
193 },
194 [OSMO_IPA_KA_S_WAIT_RESP] = {
195 .name = "WAIT_RESP",
196 .in_event_mask = S(OSMO_IPA_KA_E_PONG),
197 .out_state_mask = S(OSMO_IPA_KA_S_IDLE) | S(OSMO_IPA_KA_S_INIT),
198 .action = ipa_ka_wait_resp,
199 .onenter = ipa_ka_wait_resp_onenter,
200 },
201};
202
203static struct osmo_fsm ipa_keepalive_fsm = {
204 .name = "IPA-KEEPALIVE",
205 .states = ipa_keepalive_states,
206 .num_states = ARRAY_SIZE(ipa_keepalive_states),
207 .log_subsys = DLINP,
Harald Welte02c5e9d2019-04-06 20:22:43 +0200208 .allstate_event_mask = S(OSMO_IPA_KA_E_STOP),
Harald Welte3d60dbd2019-03-08 15:10:30 +0100209 .allstate_action = ipa_ka_allstate_action,
210 .event_names = ipa_keepalive_event_names,
211 .timer_cb = ipa_ka_fsm_timer_cb,
212};
213
214static __attribute__((constructor)) void on_dso_load(void)
215{
Vadim Yanitskiy4c2c0882020-01-01 13:59:15 +0100216 OSMO_ASSERT(osmo_fsm_register(&ipa_keepalive_fsm) == 0);
Harald Welte3d60dbd2019-03-08 15:10:30 +0100217}
218
219
220static struct osmo_fsm_inst *
221__ipa_conn_alloc_keepalive_fsm(void *ctx, const struct ipa_keepalive_params *params, const char *id)
222{
223 struct osmo_fsm_inst *fi;
224 struct ipa_fsm_priv *ifp;
225
226 fi = osmo_fsm_inst_alloc(&ipa_keepalive_fsm, ctx, NULL, LOGL_DEBUG, id);
227 if (!fi)
228 return NULL;
229 ifp = talloc_zero(fi, struct ipa_fsm_priv);
230 if (!ifp) {
231 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
232 return NULL;
233 }
234 memcpy(&ifp->params, params, sizeof(ifp->params));
235 fi->priv = ifp;
236
237 return fi;
238}
239
240/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG.
241 * \param[in] client The client connection for which to crate the FSM. Used as talloc context.
242 * \param[in] params Parameters describing the keepalive FSM time-outs.
243 * \param[in] id String used as identifier for the FSM.
244 * \returns pointer to the newly-created FSM instance; NULL in case of error. */
245struct osmo_fsm_inst *ipa_client_conn_alloc_keepalive_fsm(struct ipa_client_conn *client,
246 const struct ipa_keepalive_params *params,
247 const char *id)
248{
249 struct osmo_fsm_inst *fi;
250 struct ipa_fsm_priv *ifp;
251
252 fi = __ipa_conn_alloc_keepalive_fsm(client, params, id);
253 if (!fi)
254 return NULL;
255 ifp = fi->priv;
256 ifp->client_conn = client;
257 return fi;
258}
259
260/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG.
261 * \param[in] server The server connection for which to crate the FSM. Used as talloc context.
262 * \param[in] params Parameters describing the keepalive FSM time-outs.
263 * \param[in] id String used as identifier for the FSM.
264 * \returns pointer to the newly-created FSM instance; NULL in case of error. */
265struct osmo_fsm_inst *ipa_server_conn_alloc_keepalive_fsm(struct ipa_server_conn *server,
266 const struct ipa_keepalive_params *params,
267 const char *id)
268{
269 struct osmo_fsm_inst *fi;
270 struct ipa_fsm_priv *ifp;
271
272 fi = __ipa_conn_alloc_keepalive_fsm(server, params, id);
273 if (!fi)
274 return NULL;
275 ifp = fi->priv;
276 ifp->srv_conn = server;
277 return fi;
278}
279
Eric Wild51b61002019-07-09 13:48:06 +0200280/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG.
281 * \param[in] ctx Talloc context.
282 * \param[in] data Data to pass to write/timeout cb.
283 * \param[in] params Parameters describing the keepalive FSM time-outs.
284 * \param[in] id String used as identifier for the FSM.
285 * \returns pointer to the newly-created FSM instance; NULL in case of error. */
286struct osmo_fsm_inst *ipa_generic_conn_alloc_keepalive_fsm(void *ctx, void* data,
287 const struct ipa_keepalive_params *params,
288 const char *id)
289{
290 struct osmo_fsm_inst *fi;
291 struct ipa_fsm_priv *ifp;
292
293 fi = __ipa_conn_alloc_keepalive_fsm(ctx, params, id);
294 if (!fi)
295 return NULL;
296 ifp = fi->priv;
297 ifp->generic = data;
298 return fi;
299}
300
Harald Welte3d60dbd2019-03-08 15:10:30 +0100301/*! Set a timeout call-back which is to be called once the peer doesn't respond anymore */
302void ipa_keepalive_fsm_set_timeout_cb(struct osmo_fsm_inst *fi, ipa_keepalive_timeout_cb_t *cb)
303{
304 struct ipa_fsm_priv *ifp = fi->priv;
305 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
306 ifp->timeout_cb = cb;
307}
308
Eric Wild51b61002019-07-09 13:48:06 +0200309/*! Set a custom send callback for sending pings */
310void ipa_keepalive_fsm_set_send_cb(struct osmo_fsm_inst *fi, ipa_keepalive_send_cb_t *fn)
311{
312 struct ipa_fsm_priv *ifp = fi->priv;
313 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
314 ifp->send_fn = fn;
315}
316
Harald Welte3d60dbd2019-03-08 15:10:30 +0100317/*! Inform IPA Keepalive FSM that a PONG has been received. */
318void ipa_keepalive_fsm_pong_received(struct osmo_fsm_inst *fi)
319{
320 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
321 osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_PONG, NULL);
322}
323
324/*! Start the ping/pong procedure of the IPA Keepalive FSM. */
325void ipa_keepalive_fsm_start(struct osmo_fsm_inst *fi)
326{
327 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
328 osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_START, NULL);
329}
330
331/*! Stop the ping/pong procedure of the IPA Keepalive FSM. */
332void ipa_keepalive_fsm_stop(struct osmo_fsm_inst *fi)
333{
334 OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm);
335 osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_STOP, NULL);
336}