Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 1 | /* 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 */ |
| 38 | static 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 | |
| 50 | enum 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 | |
| 56 | enum osmo_ipa_keepalive_event { |
| 57 | OSMO_IPA_KA_E_START, |
| 58 | OSMO_IPA_KA_E_STOP, |
| 59 | OSMO_IPA_KA_E_PONG, |
| 60 | }; |
| 61 | |
| 62 | static 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 | |
| 69 | enum ipa_fsm_timer { |
| 70 | T_SEND_NEXT_PING = 1, |
| 71 | T_PONG_NOT_RECEIVED = 2, |
| 72 | }; |
| 73 | |
| 74 | struct 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 Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 79 | void *generic; |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 80 | ipa_keepalive_timeout_cb_t *timeout_cb; |
Eric Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 81 | ipa_keepalive_send_cb_t *send_fn; |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 82 | }; |
| 83 | |
| 84 | static 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 | |
| 99 | static 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 Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 108 | 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 Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 119 | else { |
| 120 | OSMO_ASSERT(ifp->client_conn); |
Eric Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 121 | 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 Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 125 | } |
| 126 | } |
| 127 | |
| 128 | static 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 | |
| 142 | static 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 Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 157 | else if (ifp->client_conn) |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 158 | conn = ifp->client_conn; |
Eric Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 159 | else |
| 160 | conn = ifp->generic; |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 161 | if (ifp->timeout_cb) |
Eric Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 162 | return ifp->timeout_cb(fi, conn); |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 163 | /* ask fsm core to terminate us */ |
| 164 | return 1; |
| 165 | default: |
| 166 | OSMO_ASSERT(0); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | static 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 | |
| 182 | static 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 Welte | 3e03bc2 | 2019-04-06 20:26:25 +0200 | [diff] [blame] | 186 | .out_state_mask = S(OSMO_IPA_KA_S_WAIT_RESP) | S(OSMO_IPA_KA_S_INIT), |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 187 | .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 | |
| 203 | static 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 Welte | 02c5e9d | 2019-04-06 20:22:43 +0200 | [diff] [blame] | 208 | .allstate_event_mask = S(OSMO_IPA_KA_E_STOP), |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 209 | .allstate_action = ipa_ka_allstate_action, |
| 210 | .event_names = ipa_keepalive_event_names, |
| 211 | .timer_cb = ipa_ka_fsm_timer_cb, |
| 212 | }; |
| 213 | |
| 214 | static __attribute__((constructor)) void on_dso_load(void) |
| 215 | { |
Vadim Yanitskiy | 4c2c088 | 2020-01-01 13:59:15 +0100 | [diff] [blame] | 216 | OSMO_ASSERT(osmo_fsm_register(&ipa_keepalive_fsm) == 0); |
Harald Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 217 | } |
| 218 | |
| 219 | |
| 220 | static 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. */ |
| 245 | struct 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. */ |
| 265 | struct 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 Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 280 | /*! 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. */ |
| 286 | struct 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 Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 301 | /*! Set a timeout call-back which is to be called once the peer doesn't respond anymore */ |
| 302 | void 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 Wild | 51b6100 | 2019-07-09 13:48:06 +0200 | [diff] [blame] | 309 | /*! Set a custom send callback for sending pings */ |
| 310 | void 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 Welte | 3d60dbd | 2019-03-08 15:10:30 +0100 | [diff] [blame] | 317 | /*! Inform IPA Keepalive FSM that a PONG has been received. */ |
| 318 | void 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. */ |
| 325 | void 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. */ |
| 332 | void 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 | } |