From 3d60dbd0214ad4125bfce8fa869d39be8376c8f1 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 8 Mar 2019 15:10:30 +0100 Subject: [PATCH] Add IPA keep-alive FSM implementation The IPA keep-alive FSM code takes care of periodically transmitting and IPA CCM PING and expecting an IPA CCM PONG in return. Change-Id: I2763da49a74de85046ac07d53592c89973314ca6 --- include/osmocom/abis/ipa.h | 36 +++++ src/Makefile.am | 1 + src/input/ipa_keepalive.c | 290 +++++++++++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 src/input/ipa_keepalive.c diff --git a/include/osmocom/abis/ipa.h b/include/osmocom/abis/ipa.h index a738156..4f6081f 100644 --- a/include/osmocom/abis/ipa.h +++ b/include/osmocom/abis/ipa.h @@ -5,6 +5,7 @@ #include #include #include +#include #include struct e1inp_line; @@ -99,4 +100,39 @@ int ipaccess_bts_handle_ccm(struct ipa_client_conn *link, void ipa_msg_push_header(struct msgb *msg, uint8_t proto); + +/*********************************************************************** + * IPA Keep-Alive FSM + ***********************************************************************/ + +/*! parameters describing the keep-alive FSM (timeouts). */ +struct ipa_keepalive_params { + /*! interval in which to send IPA CCM PING requests to the peer. */ + unsigned int interval; + /*! time to wait for an IPA CCM PONG in response to a IPA CCM PING before giving up. */ + unsigned int wait_for_resp; +}; + +typedef void ipa_keepalive_timeout_cb_t(struct osmo_fsm_inst *fi, void *conn); + +struct osmo_fsm_inst *ipa_client_conn_alloc_keepalive_fsm(struct ipa_client_conn *client, + const struct ipa_keepalive_params *params, + const char *id); + +struct osmo_fsm_inst *ipa_server_conn_alloc_keepalive_fsm(struct ipa_server_conn *server, + const struct ipa_keepalive_params *params, + const char *id); + +struct osmo_fsm_inst *ipa_keepalive_alloc_server(struct ipa_server_conn *server, + const struct ipa_keepalive_params *params, + const char *id); + +void ipa_keepalive_fsm_set_timeout_cb(struct osmo_fsm_inst *fi, ipa_keepalive_timeout_cb_t *cb); + +void ipa_keepalive_fsm_start(struct osmo_fsm_inst *fi); + +void ipa_keepalive_fsm_stop(struct osmo_fsm_inst *fi); + +void ipa_keepalive_fsm_pong_received(struct osmo_fsm_inst *fi); + #endif diff --git a/src/Makefile.am b/src/Makefile.am index 9566617..2d2424d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,6 +21,7 @@ libosmoabis_la_SOURCES = init.c \ trau_frame.c \ input/dahdi.c \ input/ipa.c \ + input/ipa_keepalive.c \ input/ipaccess.c \ input/lapd.c \ input/lapd_pcap.c \ diff --git a/src/input/ipa_keepalive.c b/src/input/ipa_keepalive.c new file mode 100644 index 0000000..1aae096 --- /dev/null +++ b/src/input/ipa_keepalive.c @@ -0,0 +1,290 @@ +/* IPA keep-alive FSM; Periodically transmit IPA_PING and expect IPA_PONG in return. + * + * (C) 2019 by Harald Welte + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include + +#include + +#include + +#define S(x) (1 << (x)) + + +/* generate a msgb containing an IPA CCM PING message */ +static struct msgb *gen_ipa_ping(void) +{ + struct msgb *msg = msgb_alloc_headroom(64, 32, "IPA PING"); + if (!msg) + return NULL; + + msgb_put_u8(msg, IPAC_MSGT_PING); + ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS); + + return msg; +} + +enum osmo_ipa_keepalive_state { + OSMO_IPA_KA_S_INIT, + OSMO_IPA_KA_S_IDLE, /* waiting for next interval */ + OSMO_IPA_KA_S_WAIT_RESP, /* waiting for response to keepalive */ +}; + +enum osmo_ipa_keepalive_event { + OSMO_IPA_KA_E_START, + OSMO_IPA_KA_E_STOP, + OSMO_IPA_KA_E_PONG, +}; + +static const struct value_string ipa_keepalive_event_names[] = { + OSMO_VALUE_STRING(OSMO_IPA_KA_E_START), + OSMO_VALUE_STRING(OSMO_IPA_KA_E_STOP), + OSMO_VALUE_STRING(OSMO_IPA_KA_E_PONG), + { 0, NULL } +}; + +enum ipa_fsm_timer { + T_SEND_NEXT_PING = 1, + T_PONG_NOT_RECEIVED = 2, +}; + +struct ipa_fsm_priv { + struct ipa_keepalive_params params; + + struct ipa_server_conn *srv_conn; + struct ipa_client_conn *client_conn; + ipa_keepalive_timeout_cb_t *timeout_cb; +}; + +static void ipa_ka_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ipa_fsm_priv *ifp = fi->priv; + + switch (event) { + case OSMO_IPA_KA_E_START: + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP, + ifp->params.wait_for_resp, T_PONG_NOT_RECEIVED); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void ipa_ka_wait_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct ipa_fsm_priv *ifp = fi->priv; + struct msgb *msg; + + /* Send an IPA PING to the peer */ + msg = gen_ipa_ping(); + OSMO_ASSERT(msg); + + if (ifp->srv_conn) + ipa_server_conn_send(ifp->srv_conn, msg); + else { + OSMO_ASSERT(ifp->client_conn); + ipa_client_conn_send(ifp->client_conn, msg); + } +} + +static void ipa_ka_wait_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ipa_fsm_priv *ifp = fi->priv; + + switch (event) { + case OSMO_IPA_KA_E_PONG: + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_IDLE, + ifp->params.interval, T_SEND_NEXT_PING); + break; + default: + OSMO_ASSERT(0); + } +} + +static int ipa_ka_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct ipa_fsm_priv *ifp = fi->priv; + void *conn; + + switch (fi->T) { + case T_SEND_NEXT_PING: + /* send another PING */ + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP, + ifp->params.wait_for_resp, T_PONG_NOT_RECEIVED); + return 0; + case T_PONG_NOT_RECEIVED: + /* PONG not received within time */ + if (ifp->srv_conn) + conn = ifp->srv_conn; + else + conn = ifp->client_conn; + if (ifp->timeout_cb) + ifp->timeout_cb(fi, conn); + /* ask fsm core to terminate us */ + return 1; + default: + OSMO_ASSERT(0); + } +} + +static void ipa_ka_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case OSMO_IPA_KA_E_STOP: + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_INIT, 0, 0); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static const struct osmo_fsm_state ipa_keepalive_states[] = { + [OSMO_IPA_KA_S_INIT] = { + .name = "INIT", + .in_event_mask = S(OSMO_IPA_KA_E_START), + .out_state_mask = S(OSMO_IPA_KA_S_WAIT_RESP), + .action = ipa_ka_init, + }, + [OSMO_IPA_KA_S_IDLE] = { + .name = "IDLE", + .out_state_mask = S(OSMO_IPA_KA_S_WAIT_RESP) | S(OSMO_IPA_KA_S_INIT), + /* no permitted events aside from E_START, which is handled in allstate_events */ + }, + [OSMO_IPA_KA_S_WAIT_RESP] = { + .name = "WAIT_RESP", + .in_event_mask = S(OSMO_IPA_KA_E_PONG), + .out_state_mask = S(OSMO_IPA_KA_S_IDLE) | S(OSMO_IPA_KA_S_INIT), + .action = ipa_ka_wait_resp, + .onenter = ipa_ka_wait_resp_onenter, + }, +}; + +static struct osmo_fsm ipa_keepalive_fsm = { + .name = "IPA-KEEPALIVE", + .states = ipa_keepalive_states, + .num_states = ARRAY_SIZE(ipa_keepalive_states), + .log_subsys = DLINP, + .allstate_action = ipa_ka_allstate_action, + .event_names = ipa_keepalive_event_names, + .timer_cb = ipa_ka_fsm_timer_cb, +}; + +static __attribute__((constructor)) void on_dso_load(void) +{ + osmo_fsm_register(&ipa_keepalive_fsm); +} + + +static struct osmo_fsm_inst * +__ipa_conn_alloc_keepalive_fsm(void *ctx, const struct ipa_keepalive_params *params, const char *id) +{ + struct osmo_fsm_inst *fi; + struct ipa_fsm_priv *ifp; + + fi = osmo_fsm_inst_alloc(&ipa_keepalive_fsm, ctx, NULL, LOGL_DEBUG, id); + if (!fi) + return NULL; + ifp = talloc_zero(fi, struct ipa_fsm_priv); + if (!ifp) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; + } + memcpy(&ifp->params, params, sizeof(ifp->params)); + fi->priv = ifp; + + return fi; +} + +/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG. + * \param[in] client The client connection for which to crate the FSM. Used as talloc context. + * \param[in] params Parameters describing the keepalive FSM time-outs. + * \param[in] id String used as identifier for the FSM. + * \returns pointer to the newly-created FSM instance; NULL in case of error. */ +struct osmo_fsm_inst *ipa_client_conn_alloc_keepalive_fsm(struct ipa_client_conn *client, + const struct ipa_keepalive_params *params, + const char *id) +{ + struct osmo_fsm_inst *fi; + struct ipa_fsm_priv *ifp; + + fi = __ipa_conn_alloc_keepalive_fsm(client, params, id); + if (!fi) + return NULL; + ifp = fi->priv; + ifp->client_conn = client; + return fi; +} + +/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG. + * \param[in] server The server connection for which to crate the FSM. Used as talloc context. + * \param[in] params Parameters describing the keepalive FSM time-outs. + * \param[in] id String used as identifier for the FSM. + * \returns pointer to the newly-created FSM instance; NULL in case of error. */ +struct osmo_fsm_inst *ipa_server_conn_alloc_keepalive_fsm(struct ipa_server_conn *server, + const struct ipa_keepalive_params *params, + const char *id) +{ + struct osmo_fsm_inst *fi; + struct ipa_fsm_priv *ifp; + + fi = __ipa_conn_alloc_keepalive_fsm(server, params, id); + if (!fi) + return NULL; + ifp = fi->priv; + ifp->srv_conn = server; + return fi; +} + +/*! Set a timeout call-back which is to be called once the peer doesn't respond anymore */ +void ipa_keepalive_fsm_set_timeout_cb(struct osmo_fsm_inst *fi, ipa_keepalive_timeout_cb_t *cb) +{ + struct ipa_fsm_priv *ifp = fi->priv; + OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm); + ifp->timeout_cb = cb; +} + +/*! Inform IPA Keepalive FSM that a PONG has been received. */ +void ipa_keepalive_fsm_pong_received(struct osmo_fsm_inst *fi) +{ + OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm); + osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_PONG, NULL); +} + +/*! Start the ping/pong procedure of the IPA Keepalive FSM. */ +void ipa_keepalive_fsm_start(struct osmo_fsm_inst *fi) +{ + OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm); + osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_START, NULL); +} + +/*! Stop the ping/pong procedure of the IPA Keepalive FSM. */ +void ipa_keepalive_fsm_stop(struct osmo_fsm_inst *fi) +{ + OSMO_ASSERT(fi->fsm == &ipa_keepalive_fsm); + osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_STOP, NULL); +}