pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmo-pfcp/+/42120?usp=email )
Change subject: cp_peer: Implement local originated heartbeat procedure ......................................................................
cp_peer: Implement local originated heartbeat procedure
Submit PFCP Hearbeat Request with configured interval, and timeout after no PFCP Hearbeat Response received based on configuration.
Upon timeout, the cp_peer assoc_cb() is called to notify the user that the peer is considered not associated anymore. It will then try to keep associating again automatically.
Related: SYS#7294 Change-Id: I7efc0961e1ea39dd7f4cc6ba96be4cf5ce9a2d6c --- M include/osmocom/pfcp/Makefile.am M include/osmocom/pfcp/pfcp_cp_peer_private.h A include/osmocom/pfcp/pfcp_endpoint_private.h M src/libosmo-pfcp/pfcp_cp_peer.c M src/libosmo-pfcp/pfcp_endpoint.c 5 files changed, 163 insertions(+), 33 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmo-pfcp refs/changes/20/42120/1
diff --git a/include/osmocom/pfcp/Makefile.am b/include/osmocom/pfcp/Makefile.am index 21b3aeb..b4a2305 100644 --- a/include/osmocom/pfcp/Makefile.am +++ b/include/osmocom/pfcp/Makefile.am @@ -1,4 +1,5 @@ noinst_HEADERS = \ + pfcp_endpoint_private.h \ pfcp_cp_peer_private.h \ $(NULL)
diff --git a/include/osmocom/pfcp/pfcp_cp_peer_private.h b/include/osmocom/pfcp/pfcp_cp_peer_private.h index b8b0af7..503912d 100644 --- a/include/osmocom/pfcp/pfcp_cp_peer_private.h +++ b/include/osmocom/pfcp/pfcp_cp_peer_private.h @@ -46,6 +46,9 @@ /* Application private data for assoc_cb, in case ep->priv does not suffice. */ void *priv;
+ struct osmo_timer_list heartbeat_tx_timer; + struct osmo_timer_list heartbeat_rx_timer; + struct osmo_use_count use_count; struct osmo_use_count_entry use_count_buf[128]; }; diff --git a/include/osmocom/pfcp/pfcp_endpoint_private.h b/include/osmocom/pfcp/pfcp_endpoint_private.h new file mode 100644 index 0000000..2b1b52b --- /dev/null +++ b/include/osmocom/pfcp/pfcp_endpoint_private.h @@ -0,0 +1,57 @@ +/* + * (C) 2021-2025 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved. + * + * Author: Neels Janosch Hofmeyr nhofmeyr@sysmocom.de + * + * 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, see http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include <stdint.h> + +#include <osmocom/core/hashtable.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/osmo_io.h> +#include <osmocom/pfcp/pfcp_endpoint.h> + + +/* Send/receive PFCP messages to/from remote PFCP endpoints. */ +struct osmo_pfcp_endpoint { + struct osmo_pfcp_endpoint_cfg cfg; + + /* PFCP socket */ + struct osmo_io_fd *iofd; + + /* The time at which this endpoint last restarted, as seconds since unix epoch. */ + uint32_t recovery_time_stamp; + + /* State for determining the next sequence number for transmitting a request message */ + uint32_t seq_nr_state; + + /* All transmitted PFCP Request messages, list of osmo_pfcp_queue_entry. + * For a transmitted Request message, wait for a matching Response from a remote peer; if none arrives, + * retransmit (see n1 and t1_ms). */ + struct llist_head sent_requests; + DECLARE_HASHTABLE(sent_requests_by_seq_nr, 12); + /* All transmitted PFCP Response messages, list of osmo_pfcp_queue_entry. + * For a transmitted Response message, keep it in the queue for a fixed amount of time. If the peer retransmits + * the original Request, do not dispatch the Request, but respond with the queued message directly. */ + struct llist_head sent_responses; + DECLARE_HASHTABLE(sent_responses_by_seq_nr, 12); +}; diff --git a/src/libosmo-pfcp/pfcp_cp_peer.c b/src/libosmo-pfcp/pfcp_cp_peer.c index 403a474..e48810f 100644 --- a/src/libosmo-pfcp/pfcp_cp_peer.c +++ b/src/libosmo-pfcp/pfcp_cp_peer.c @@ -27,7 +27,7 @@ #include <osmocom/core/fsm.h> #include <osmocom/core/tdef.h>
-#include <osmocom/pfcp/pfcp_endpoint.h> +#include <osmocom/pfcp/pfcp_endpoint_private.h> #include <osmocom/pfcp/pfcp_cp_peer_private.h>
#define LOG_CP_PEER(CP_PEER, LOGLEVEL, FMT, ARGS...) \ @@ -45,12 +45,14 @@ enum pfcp_cp_peer_fsm_event { PFCP_CP_PEER_EV_RX_ASSOC_SETUP_RESP, PFCP_CP_PEER_EV_RX_ASSOC_UPDATE_REQ, + PFCP_CP_PEER_EV_HEARTBEAT_TIMEOUT, PFCP_CP_PEER_EV_USE_COUNT_ZERO, };
static const struct value_string pfcp_cp_peer_fsm_event_names[] = { OSMO_VALUE_STRING(PFCP_CP_PEER_EV_RX_ASSOC_SETUP_RESP), OSMO_VALUE_STRING(PFCP_CP_PEER_EV_RX_ASSOC_UPDATE_REQ), + OSMO_VALUE_STRING(PFCP_CP_PEER_EV_HEARTBEAT_TIMEOUT), OSMO_VALUE_STRING(PFCP_CP_PEER_EV_USE_COUNT_ZERO), {} }; @@ -91,6 +93,62 @@ return 0; }
+static int on_pfcp_heartbeat_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg) +{ + struct osmo_fsm_inst *fi = req->ctx.peer_fi; + struct osmo_pfcp_cp_peer *cp_peer = fi->priv; + + if (!rx_resp) { + OSMO_LOG_PFCP_MSG(req, LOGL_NOTICE, "Error: PFCP Heartbeat Response: %s\n", + errmsg ? : "no response received"); + return 0; + } + + OSMO_LOG_PFCP_MSG(rx_resp, LOGL_INFO, "Rx PFCP Heartbeat Response\n"); + + if (fi->state != PFCP_CP_PEER_ST_ASSOCIATED) + return 0; + + unsigned int tval_rx_heartbeat_s = + osmo_tdef_get(cp_peer->ep->cfg.tdefs, OSMO_PFCP_TIMER_HEARTBEAT_RESP, OSMO_TDEF_S, -1); + osmo_timer_schedule(&cp_peer->heartbeat_rx_timer, tval_rx_heartbeat_s, 0); + + return 0; +} + +static void pfcp_cp_peer_tx_heartbeat_req(struct osmo_pfcp_cp_peer *cp_peer) +{ + struct osmo_pfcp_msg *m; + + m = osmo_pfcp_cp_peer_new_req(cp_peer, OSMO_PFCP_MSGT_HEARTBEAT_REQ); + m->ies.heartbeat_req.recovery_time_stamp = osmo_pfcp_endpoint_get_recovery_timestamp(cp_peer->ep); + + m->ctx.resp_cb = on_pfcp_heartbeat_resp; + + LOG_CP_PEER(cp_peer, LOGL_INFO, "Tx PFCP Heartbeat Request\n"); + + if (osmo_pfcp_endpoint_tx(cp_peer->ep, m)) + LOG_CP_PEER(cp_peer, LOGL_ERROR, "Failed to transmit PFCP Heartbeat Request to peer\n"); +} + +static void pfcp_cp_peer_heartbeat_tx_timer_cb(void *data) +{ + struct osmo_pfcp_cp_peer *cp_peer = data; + unsigned int tval_tx_heartbeat_s = + osmo_tdef_get(cp_peer->ep->cfg.tdefs, OSMO_PFCP_TIMER_HEARTBEAT_REQ, OSMO_TDEF_S, -1); + + pfcp_cp_peer_tx_heartbeat_req(cp_peer); + + osmo_timer_schedule(&cp_peer->heartbeat_tx_timer, tval_tx_heartbeat_s, 0); +} + +static void pfcp_cp_peer_heartbeat_rx_timer_cb(void *data) +{ + struct osmo_pfcp_cp_peer *cp_peer = data; + + osmo_fsm_inst_dispatch(cp_peer->fi, PFCP_CP_PEER_EV_HEARTBEAT_TIMEOUT, NULL); +} + /* Allocate PFCP CP peer FSM and start sending PFCP Association Setup Request messages to remote_addr, using endpoint * ep. As soon as a successful response is received, change to state PFCP_CP_PEER_ST_ASSOCIATED. */ @@ -117,9 +175,20 @@ osmo_use_count_make_static_entries(&cp_peer->use_count, cp_peer->use_count_buf, ARRAY_SIZE(cp_peer->use_count_buf));
osmo_fsm_inst_update_id_f_sanitize(fi, '-', osmo_sockaddr_to_str_c(OTC_SELECT, &cp_peer->remote_addr)); + + osmo_timer_setup(&cp_peer->heartbeat_tx_timer, pfcp_cp_peer_heartbeat_tx_timer_cb, cp_peer); + osmo_timer_setup(&cp_peer->heartbeat_rx_timer, pfcp_cp_peer_heartbeat_rx_timer_cb, cp_peer); return cp_peer; }
+ +static void pfcp_cp_peer_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct osmo_pfcp_cp_peer *cp_peer = fi->priv; + osmo_timer_del(&cp_peer->heartbeat_tx_timer); + osmo_timer_del(&cp_peer->heartbeat_rx_timer); +} + void osmo_pfcp_cp_peer_free(struct osmo_pfcp_cp_peer *cp_peer) { if (!cp_peer) @@ -257,8 +326,18 @@ static void pfcp_cp_peer_associated_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct osmo_pfcp_cp_peer *cp_peer = fi->priv; + unsigned int tval_rx_heartbeat_s = + osmo_tdef_get(cp_peer->ep->cfg.tdefs, OSMO_PFCP_TIMER_HEARTBEAT_RESP, OSMO_TDEF_S, -1); LOG_CP_PEER(cp_peer, LOGL_NOTICE, "Associated with UPF %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, &cp_peer->remote_addr)); + + /* Send first Hrartbeat Req immediately to fetch recovery timestamp info from peer. + * 3GPP TS 23.007 19A: "When peer PFCP entities information is available, i.e. when the PFCP Association + * is still alive, the restarted PFCP entity shall send its updated Recovery Time Stamps in a Heartbeat + * Request message to the peer PFCP entities before initiating any PFCP session signalling" + */ + osmo_timer_schedule(&cp_peer->heartbeat_tx_timer, 0, 0); + osmo_timer_schedule(&cp_peer->heartbeat_rx_timer, tval_rx_heartbeat_s, 0); if (cp_peer->assoc_cb) cp_peer->assoc_cb(cp_peer, true); } @@ -273,6 +352,11 @@ LOG_CP_PEER(cp_peer, LOGL_ERROR, "PFCP Association Update Request is not implemented\n"); break;
+ case PFCP_CP_PEER_EV_HEARTBEAT_TIMEOUT: + LOG_CP_PEER(cp_peer, LOGL_NOTICE, "Heartbeat timeout!\n"); + osmo_pfcp_cp_peer_fsm_state_chg(PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP); + break; + default: OSMO_ASSERT(false); } @@ -283,6 +367,8 @@ struct osmo_pfcp_cp_peer *cp_peer = fi->priv; LOG_CP_PEER(cp_peer, LOGL_NOTICE, "Disassociating from UPF %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, &cp_peer->remote_addr)); + osmo_timer_del(&cp_peer->heartbeat_tx_timer); + osmo_timer_del(&cp_peer->heartbeat_rx_timer); if (cp_peer->assoc_cb) cp_peer->assoc_cb(cp_peer, false); } @@ -358,6 +444,7 @@ .name = "associated", .in_event_mask = 0 | S(PFCP_CP_PEER_EV_RX_ASSOC_UPDATE_REQ) + | S(PFCP_CP_PEER_EV_HEARTBEAT_TIMEOUT) , .out_state_mask = 0 | S(PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP) @@ -394,6 +481,7 @@ .num_states = ARRAY_SIZE(pfcp_cp_peer_fsm_states), .log_subsys = DLPFCP, .event_names = pfcp_cp_peer_fsm_event_names, + .cleanup = pfcp_cp_peer_fsm_cleanup, .timer_cb = pfcp_cp_peer_fsm_timer_cb, .allstate_action = pfcp_cp_peer_allstate_action, .allstate_event_mask = 0 diff --git a/src/libosmo-pfcp/pfcp_endpoint.c b/src/libosmo-pfcp/pfcp_endpoint.c index 64ea347..0198582 100644 --- a/src/libosmo-pfcp/pfcp_endpoint.c +++ b/src/libosmo-pfcp/pfcp_endpoint.c @@ -33,32 +33,9 @@ #include <osmocom/core/osmo_io.h>
#include <osmocom/pfcp/pfcp_endpoint.h> +#include <osmocom/pfcp/pfcp_endpoint_private.h> #include <osmocom/pfcp/pfcp_msg.h>
-/* Send/receive PFCP messages to/from remote PFCP endpoints. */ -struct osmo_pfcp_endpoint { - struct osmo_pfcp_endpoint_cfg cfg; - - /* PFCP socket */ - struct osmo_io_fd *iofd; - - /* The time at which this endpoint last restarted, as seconds since unix epoch. */ - uint32_t recovery_time_stamp; - - /* State for determining the next sequence number for transmitting a request message */ - uint32_t seq_nr_state; - - /* All transmitted PFCP Request messages, list of osmo_pfcp_queue_entry. - * For a transmitted Request message, wait for a matching Response from a remote peer; if none arrives, - * retransmit (see n1 and t1_ms). */ - struct llist_head sent_requests; - DECLARE_HASHTABLE(sent_requests_by_seq_nr, 12); - /* All transmitted PFCP Response messages, list of osmo_pfcp_queue_entry. - * For a transmitted Response message, keep it in the queue for a fixed amount of time. If the peer retransmits - * the original Request, do not dispatch the Request, but respond with the queued message directly. */ - struct llist_head sent_responses; - DECLARE_HASHTABLE(sent_responses_by_seq_nr, 12); -};
/*! Entry of pfcp_endpoint message queue of PFCP messages, for re-transsions. */ struct osmo_pfcp_queue_entry { @@ -151,14 +128,18 @@ return ep; }
-static unsigned int ep_n1(struct osmo_pfcp_endpoint *ep) +static unsigned int ep_n1(struct osmo_pfcp_endpoint *ep, const struct osmo_pfcp_msg *m) { + if (m->h.message_type == OSMO_PFCP_MSGT_HEARTBEAT_REQ) + return 0; return osmo_tdef_get(ep->cfg.tdefs, OSMO_PFCP_TIMER_N1, OSMO_TDEF_CUSTOM, -1); }
-static unsigned int ep_t1(struct osmo_pfcp_endpoint *ep) +static unsigned int ep_t1(struct osmo_pfcp_endpoint *ep, const struct osmo_pfcp_msg *m) { - return osmo_tdef_get(ep->cfg.tdefs, OSMO_PFCP_TIMER_T1, OSMO_TDEF_MS, -1); + int T = m->h.message_type == OSMO_PFCP_MSGT_HEARTBEAT_REQ ? OSMO_PFCP_TIMER_HEARTBEAT_RESP : + OSMO_PFCP_TIMER_T1; + return osmo_tdef_get(ep->cfg.tdefs, T, OSMO_TDEF_MS, -1); }
static unsigned int ep_keep_resp(const struct osmo_pfcp_endpoint *ep, const struct osmo_pfcp_msg *m) @@ -177,8 +158,8 @@ static bool pfcp_queue_retrans(struct osmo_pfcp_queue_entry *qe) { struct osmo_pfcp_endpoint *endpoint = qe->ep; - unsigned int t1_ms = ep_t1(endpoint); struct osmo_pfcp_msg *m = qe->m; + unsigned int t1_ms = ep_t1(endpoint, m); int rc;
/* if no more attempts remaining, drop from queue */ @@ -283,12 +264,12 @@ timeout_ms = ep_keep_resp(endpoint, m); OSMO_LOG_PFCP_MSG(m, LOGL_DEBUG, "keep sent Responses for %ums\n", timeout_ms); } else { - timeout_ms = ep_t1(endpoint); - n1 = ep_n1(endpoint); + timeout_ms = ep_t1(endpoint, m); + n1 = ep_n1(endpoint, m);
OSMO_LOG_PFCP_MSG(m, LOGL_DEBUG, "retransmit unanswered Requests %u x %ums\n", n1, timeout_ms); - /* If there are no retransmissions or no timeout, it makes no sense to add to the queue. */ - if (!n1 || !timeout_ms) { + /* If there are no retransmissions and no timeout, it makes no sense to add to the queue. */ + if (!n1 && !timeout_ms) { if (!m->is_response && m->ctx.resp_cb) m->ctx.resp_cb(m, NULL, "PFCP timeout is zero, cannot wait for a response"); return 0;