pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmo-netif/+/39035?usp=email )
Change subject: ipa: Add osmo_ipa_ka_fsm_inst APIs ......................................................................
ipa: Add osmo_ipa_ka_fsm_inst APIs
This new object and its APIs are meant to replace ipa_keepalive_fsm_* APIs from libosmo-abis, because: 1- libosmo-abis is not the proper place to have IPA generic code, since other protocols than Abis also use IPA underneath. 2- the existing API in libosmo-abis is actually a set of 3 interfaces, 2 of which rely on ipa_client_conn and ipa_server_conn, which are provided by libosmo-abis itself and which will eventually be deprecated. Hence, only the "generic" one is actually reimplemented here, so it is backend agnsotic.
A notable difference from the FSM implementation in libosmo-abis is that this fsm instnace object doesn't terminate itself, but leaves the decision to the user, in order to allow for more flexibility during object destruction.
Change-Id: I5c36e06e0dc29ec4679b20ad6c426f051b659acd --- M TODO-RELEASE M include/osmocom/netif/ipa.h M src/Makefile.am A src/ipa_keepalive.c 4 files changed, 364 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmo-netif refs/changes/35/39035/1
diff --git a/TODO-RELEASE b/TODO-RELEASE index 268b142..5092ee7 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -9,3 +9,4 @@ #library what description / commit summary line libosmo-netif add API osmo_stream_cli_set_{ip_dscp,priority}(), osmo_stream_srv_link_set_{ip_dscp,priority}() libosmo-netif add API osmo-stream_cli_set_tx_queue_max_length(), osmo_stream_srv_link_set_tx_queue_max_length() +libosmo-netif add API struct osmo_ipa_ka_fsm_inst \ No newline at end of file diff --git a/include/osmocom/netif/ipa.h b/include/osmocom/netif/ipa.h index 038b9ac..74f3428 100644 --- a/include/osmocom/netif/ipa.h +++ b/include/osmocom/netif/ipa.h @@ -67,4 +67,27 @@
void osmo_ipa_msg_push_headers(struct msgb *msg, enum ipaccess_proto p, enum ipaccess_proto_ext pe);
+/*********************************************************************** + * IPA Keep-Alive FSM + ***********************************************************************/ +struct osmo_ipa_ka_fsm_inst; +typedef int (*osmo_ipa_ka_fsm_timeout_cb_t)(struct osmo_ipa_ka_fsm_inst *ka_fi); + +typedef int (*osmo_ipa_ka_fsm_send_cb_t)(struct osmo_ipa_ka_fsm_inst *ka_fi, struct msgb *msg); + +struct osmo_ipa_ka_fsm_inst *osmo_ipa_ka_fsm_alloc(void *ctx, const char *id); +void osmo_ipa_ka_fsm_free(struct osmo_ipa_ka_fsm_inst *ka_fi); + +int osmo_ipa_ka_fsm_set_ping_interval(struct osmo_ipa_ka_fsm_inst *ka_fi, unsigned int interval); +int osmo_ipa_ka_fsm_set_pong_timeout(struct osmo_ipa_ka_fsm_inst *ka_fi, unsigned int timeout); +void osmo_ipa_ka_fsm_set_data(struct osmo_ipa_ka_fsm_inst *ka_fi, void *cb_data); +void *osmo_ipa_ka_fsm_get_data(const struct osmo_ipa_ka_fsm_inst *ka_fi); + +void osmo_ipa_ka_fsm_set_timeout_cb(struct osmo_ipa_ka_fsm_inst *ka_fi, osmo_ipa_ka_fsm_timeout_cb_t timeout_cb); +void osmo_ipa_ka_fsm_set_send_cb(struct osmo_ipa_ka_fsm_inst *ka_fi, osmo_ipa_ka_fsm_send_cb_t send_cb); + +void osmo_ipa_ka_fsm_start(struct osmo_ipa_ka_fsm_inst *ka_fi); +void osmo_ipa_ka_fsm_pong_received(struct osmo_ipa_ka_fsm_inst *ka_fi); +void osmo_ipa_ka_fsm_stop(struct osmo_ipa_ka_fsm_inst *ka_fi); + #endif diff --git a/src/Makefile.am b/src/Makefile.am index 258d04a..bd3f272 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ libosmonetif_la_SOURCES = amr.c \ datagram.c \ ipa.c \ + ipa_keepalive.c \ ipa_unit.c \ jibuf.c \ osmux.c \ diff --git a/src/ipa_keepalive.c b/src/ipa_keepalive.c new file mode 100644 index 0000000..9067404 --- /dev/null +++ b/src/ipa_keepalive.c @@ -0,0 +1,339 @@ +/* IPA keep-alive FSM; Periodically transmit IPA_PING and expect IPA_PONG in return. + * + * (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de + * (C) 2019 by Harald Welte laforge@gnumonks.org + * + * 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. + * + */ + +#include <osmocom/core/fsm.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> + +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/netif/ipa.h> + +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 } +}; + +struct osmo_ipa_ka_fsm_inst { + struct osmo_fsm_inst *fi; + /*! interval in which to send IPA CCM PING requests to the peer. */ + unsigned int ping_interval; + /*! time to wait for an IPA CCM PONG in response to a IPA CCM PING before giving up. */ + unsigned int pong_timeout; + osmo_ipa_ka_fsm_timeout_cb_t timeout_cb; + osmo_ipa_ka_fsm_send_cb_t send_cb; + void *cb_data; +}; + +/* 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_prepend_header(msg, IPAC_PROTO_IPACCESS); + + return msg; +} + +/******** + * FSM: + *******/ + +#define S(x) (1 << (x)) + +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 ipa_fsm_timer { + T_SEND_NEXT_PING = 1, + T_PONG_NOT_RECEIVED = 2, +}; + +static void ipa_ka_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_ipa_ka_fsm_inst *ka_fi = fi->priv; + + switch (event) { + case OSMO_IPA_KA_E_START: + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP, + ka_fi->pong_timeout, 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 osmo_ipa_ka_fsm_inst *ka_fi = fi->priv; + struct msgb *msg; + + if (!ka_fi->send_cb) + osmo_panic("osmo_ipa_ka_fsm_inst running without send_cb, fix your code!"); + + /* Send an IPA PING to the peer */ + msg = gen_ipa_ping(); + OSMO_ASSERT(msg); + + ka_fi->send_cb(ka_fi, msg); +} + +static void ipa_ka_wait_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_ipa_ka_fsm_inst *ka_fi = fi->priv; + + switch (event) { + case OSMO_IPA_KA_E_PONG: + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_IDLE, + ka_fi->ping_interval, T_SEND_NEXT_PING); + break; + default: + OSMO_ASSERT(0); + } +} + +static int ipa_ka_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct osmo_ipa_ka_fsm_inst *ka_fi = fi->priv; + + switch (fi->T) { + case T_SEND_NEXT_PING: + /* send another PING */ + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_WAIT_RESP, + ka_fi->pong_timeout, T_PONG_NOT_RECEIVED); + return 0; + case T_PONG_NOT_RECEIVED: + /* PONG not received within time */ + LOGPFSML(fi, LOGL_NOTICE, "IPA keep-alive FSM timed out: PONG not received\n"); + /* Keep FSM alive, move to INIT state */ + osmo_fsm_inst_state_chg(fi, OSMO_IPA_KA_S_INIT, 0, 0); + if (ka_fi->timeout_cb) + ka_fi->timeout_cb(ka_fi); + return 0; + 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) | S(OSMO_IPA_KA_S_INIT), + .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_event_mask = S(OSMO_IPA_KA_E_STOP), + .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_ASSERT(osmo_fsm_register(&ipa_keepalive_fsm) == 0); +} + + +/***************************** + * osmo_ipa_ka_fsm_inst APIS +******************************/ + +/*! Create a new instance of an IPA keepalive FSM: Periodically transmit PING and expect PONG. + * \param[in] ctx Talloc context. + * \param[in] id String used as identifier for the FSM. + * \returns pointer to the newly-created FSM instance; NULL in case of error. + * + * Must be freed with \ref osmo_ipa_ka_fsm_free() + */ +struct osmo_ipa_ka_fsm_inst *osmo_ipa_ka_fsm_alloc(void *ctx, const char *id) +{ + struct osmo_ipa_ka_fsm_inst *ka_fi; + + ka_fi = talloc_zero(ctx, struct osmo_ipa_ka_fsm_inst); + if (ka_fi) + goto ret_free; + + ka_fi->fi = osmo_fsm_inst_alloc(&ipa_keepalive_fsm, ka_fi, NULL, LOGL_DEBUG, id); + if (!ka_fi->fi) + goto ret_free; + ka_fi->fi->priv = ka_fi; + + /* TODO: set default ping_interval and pong_timeout */ + + return ka_fi; + +ret_free: + talloc_free(ka_fi); + return NULL; +} + +/*! Free object allocated through \ref osmo_ipa_ka_fsm_alloc(). + * \param[in] ka_fi IPA keepalive FSM instance. + * + * Does nothing if NULL is passed. + */ +void osmo_ipa_ka_fsm_free(struct osmo_ipa_ka_fsm_inst *ka_fi) +{ + if (!ka_fi) + return; + + osmo_fsm_inst_free(ka_fi->fi); + ka_fi->fi = NULL; + + talloc_free(ka_fi); +} + +/*! Set PING interval value. + * \param[in] ka_fi IPA keepalive FSM instance. + * \param[in] interval PING interval value, in seconds. + * \returns zero on success, negative on error. + */ +int osmo_ipa_ka_fsm_set_ping_interval(struct osmo_ipa_ka_fsm_inst *ka_fi, unsigned int interval) +{ + ka_fi->ping_interval = interval; + return 0; +} + +/*! Set PONG timeout value. + * \param[in] ka_fi IPA keepalive FSM instance. + * \param[in] timeout PONG timeout value, in seconds. + * \returns zero on success, negative on error. + */ +int osmo_ipa_ka_fsm_set_pong_timeout(struct osmo_ipa_ka_fsm_inst *ka_fi, unsigned int timeout) +{ + ka_fi->pong_timeout = timeout; + return 0; +} + +/*! Set user private data which can be used by user of osmo_ipa_ka_fsm. + * \param[in] ka_fi IPA keepalive FSM instance. + * \param[in] cb_data User private data pointer. + */ +void osmo_ipa_ka_fsm_set_data(struct osmo_ipa_ka_fsm_inst *ka_fi, void *cb_data) +{ + ka_fi->cb_data = cb_data; +} + +/*! Get user private data set previously throuhg \ref osmo_ipa_ka_fsm_set_data. + * \param[in] ka_fi IPA keepalive FSM instance. + */ +void *osmo_ipa_ka_fsm_get_data(const struct osmo_ipa_ka_fsm_inst *ka_fi) +{ + return ka_fi->cb_data; +} + +/*! Set a timeout call-back which is to be called once the peer doesn't respond anymore. + * \param[in] ka_fi IPA keepalive FSM instance. + * \param[in] timeout_cb Function to call whenever PONG timeout occurs. + * + * When the PONG timeout occurs, the FSM will stop and transition to INITIAL + * state prior to triggering the timeout_cb(). This lets the user either destroy + * the FSM (|ref osmo_ipa_ka_fsm_free()) or restart it (\ref osmo_ipa_ka_fsm_start()). + */ +void osmo_ipa_ka_fsm_set_timeout_cb(struct osmo_ipa_ka_fsm_inst *ka_fi, osmo_ipa_ka_fsm_timeout_cb_t timeout_cb) +{ + ka_fi->timeout_cb = timeout_cb; +} + +/*! Set a custom send callback for sending pings + * \param[in] ka_fi IPA keepalive FSM instance. + * \param[in] send_cb Function to call whenever a PING needs to be sent (present in msgb param). + */ +void osmo_ipa_ka_fsm_set_send_cb(struct osmo_ipa_ka_fsm_inst *ka_fi, osmo_ipa_ka_fsm_send_cb_t send_cb) +{ + ka_fi->send_cb = send_cb; +} + +/*! Start the ping/pong procedure of the IPA Keepalive FSM. + * \param[in] ka_fi IPA keepalive FSM instance. + */ +void osmo_ipa_ka_fsm_start(struct osmo_ipa_ka_fsm_inst *ka_fi) +{ + struct osmo_fsm_inst *fi = ka_fi->fi; + LOGPFSML(fi, LOGL_INFO, "Starting IPA keep-alive FSM (interval=%us wait=%us)\n", + ka_fi->ping_interval, ka_fi->pong_timeout); + osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_START, NULL); +} + +/*! Inform IPA Keepalive FSM that a PONG has been received. + * \param[in] ka_fi IPA keepalive FSM instance. + */ +void osmo_ipa_ka_fsm_pong_received(struct osmo_ipa_ka_fsm_inst *ka_fi) +{ + osmo_fsm_inst_dispatch(ka_fi->fi, OSMO_IPA_KA_E_PONG, NULL); +} + +/*! Stop the ping/pong procedure of the IPA Keepalive FSM. + * \param[in] ka_fi IPA keepalive FSM instance. + */ +void osmo_ipa_ka_fsm_stop(struct osmo_ipa_ka_fsm_inst *ka_fi) +{ + struct osmo_fsm_inst *fi = ka_fi->fi; + LOGPFSML(fi, LOGL_INFO, "Stopping IPA keep-alive FSM\n"); + osmo_fsm_inst_dispatch(fi, OSMO_IPA_KA_E_STOP, NULL); +}