/* * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH * All Rights Reserved. * * Author: Neels Janosch Hofmeyr * * 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 . * */ #include #include #include #include #include #include #define LOG_CP_PEER(CP_PEER, LOGLEVEL, FMT, ARGS...) \ LOGPFSML((CP_PEER)->fi, LOGLEVEL, FMT, ##ARGS) enum pfcp_cp_peer_fsm_state { PFCP_CP_PEER_ST_DISABLED, PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP, PFCP_CP_PEER_ST_WAIT_RETRY, PFCP_CP_PEER_ST_ASSOCIATED, PFCP_CP_PEER_ST_GRACEFUL_RELEASE, PFCP_CP_PEER_ST_WAIT_USE_COUNT, }; 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_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_USE_COUNT_ZERO), {} }; static struct osmo_fsm pfcp_cp_peer_fsm; static const struct osmo_tdef_state_timeout pfcp_cp_peer_fsm_timeouts[32] = { [PFCP_CP_PEER_ST_WAIT_RETRY] = { .T = -26 }, /* PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP is terminated by the on_pfcp_assoc_resp() callback */ [PFCP_CP_PEER_ST_GRACEFUL_RELEASE] = { .T = -21 }, }; /* Transition to a state, using the T timer defined in pfcp_cp_peer_fsm_timeouts. * Assumes local variable fi exists. */ #define osmo_pfcp_cp_peer_fsm_state_chg(state) \ osmo_tdef_fsm_inst_state_chg(fi, state, \ pfcp_cp_peer_fsm_timeouts, \ osmo_pfcp_tdefs, \ 5) static int pfcp_cp_peer_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line) { struct osmo_pfcp_cp_peer *cp_peer = e->use_count->talloc_object; if (!e->use) return -EINVAL; LOGPFSMSLSRC(cp_peer->fi, DLPFCP, LOGL_DEBUG, file, line, "%s %s: now used by %s\n", e->count > old_use_count ? "+" : "-", e->use, osmo_use_count_to_str_c(OTC_SELECT, &cp_peer->use_count)); if (e->count < 0) return -ERANGE; if (osmo_use_count_total(&cp_peer->use_count) == 0) osmo_fsm_inst_dispatch(cp_peer->fi, PFCP_CP_PEER_EV_USE_COUNT_ZERO, NULL); return 0; } /* 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. */ struct osmo_pfcp_cp_peer *osmo_pfcp_cp_peer_alloc(void *ctx, struct osmo_pfcp_endpoint *ep, const struct osmo_sockaddr *remote_addr) { struct osmo_pfcp_cp_peer *cp_peer; struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&pfcp_cp_peer_fsm, ctx, NULL, LOGL_DEBUG, NULL); OSMO_ASSERT(fi); cp_peer = talloc(fi, struct osmo_pfcp_cp_peer); OSMO_ASSERT(cp_peer); fi->priv = cp_peer; *cp_peer = (struct osmo_pfcp_cp_peer){ .fi = fi, .ep = ep, .remote_addr = *remote_addr, .use_count = { .talloc_object = cp_peer, .use_cb = pfcp_cp_peer_use_cb, }, }; 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)); return cp_peer; } int osmo_pfcp_cp_peer_associate(struct osmo_pfcp_cp_peer *cp_peer) { struct osmo_fsm_inst *fi = cp_peer->fi; switch (fi->state) { case PFCP_CP_PEER_ST_DISABLED: case PFCP_CP_PEER_ST_WAIT_RETRY: /* Idling. Send Association Setup Request now. */ return osmo_pfcp_cp_peer_fsm_state_chg(PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP); default: /* Already associated, or busy associating. */ return 0; case PFCP_CP_PEER_ST_WAIT_USE_COUNT: /* Already asked to deallocate. */ return -ENOLINK; } return 0; } static int pfcp_cp_peer_fsm_timer_cb(struct osmo_fsm_inst *fi) { switch (fi->state) { case PFCP_CP_PEER_ST_WAIT_RETRY: osmo_pfcp_cp_peer_fsm_state_chg(PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP); return 0; default: return 1; } } static int on_pfcp_assoc_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg); /* Send PFCP Association Setup Request */ static void pfcp_cp_peer_wait_assoc_setup_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct osmo_pfcp_cp_peer *cp_peer = fi->priv; struct osmo_pfcp_msg *m; m = osmo_pfcp_cp_peer_new_req(cp_peer, OSMO_PFCP_MSGT_ASSOC_SETUP_REQ); m->ies.assoc_setup_req.recovery_time_stamp = osmo_pfcp_endpoint_get_recovery_timestamp(cp_peer->ep); m->ies.assoc_setup_req.cp_function_features_present = true; osmo_pfcp_bits_set(m->ies.assoc_setup_req.cp_function_features.bits, OSMO_PFCP_CP_FEAT_BUNDL, true); m->ctx.resp_cb = on_pfcp_assoc_resp; LOG_CP_PEER(cp_peer, LOGL_NOTICE, "Associating with %s...\n", osmo_sockaddr_to_str_c(OTC_SELECT, &cp_peer->remote_addr)); if (osmo_pfcp_endpoint_tx(cp_peer->ep, m)) { LOG_CP_PEER(cp_peer, LOGL_ERROR, "Failed to transmit PFCP Association Setup Request to UPF at %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, &cp_peer->remote_addr)); osmo_pfcp_cp_peer_fsm_state_chg(PFCP_CP_PEER_ST_WAIT_RETRY); } } static int on_pfcp_assoc_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; enum osmo_pfcp_cause *cause; if (!rx_resp) { LOG_CP_PEER(cp_peer, LOGL_ERROR, "Error: PFCP Association Setup Response: %s\n", errmsg ? : "no response received"); goto assoc_failed; } cause = osmo_pfcp_msg_cause(rx_resp); if (!cause) { LOG_CP_PEER(cp_peer, LOGL_ERROR, "Invalid PFCP Association Setup Response: no Cause value\n"); goto assoc_failed; } if (*cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) { LOG_CP_PEER(cp_peer, LOGL_ERROR, "UPF rejected PFCP Association Setup Request with Cause: %s\n", osmo_pfcp_cause_str(*cause)); goto assoc_failed; } osmo_fsm_inst_dispatch(fi, PFCP_CP_PEER_EV_RX_ASSOC_SETUP_RESP, rx_resp); return 0; assoc_failed: osmo_pfcp_cp_peer_fsm_state_chg(PFCP_CP_PEER_ST_WAIT_RETRY); return 0; } static void pfcp_cp_peer_wait_assoc_setup_resp_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case PFCP_CP_PEER_EV_RX_ASSOC_SETUP_RESP: osmo_pfcp_cp_peer_fsm_state_chg(PFCP_CP_PEER_ST_ASSOCIATED); break; default: OSMO_ASSERT(false); } } 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; LOG_CP_PEER(cp_peer, LOGL_NOTICE, "Associated with UPF %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, &cp_peer->remote_addr)); if (cp_peer->assoc_cb) cp_peer->assoc_cb(cp_peer, true); } static void pfcp_cp_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct osmo_pfcp_cp_peer *cp_peer = fi->priv; switch (event) { case PFCP_CP_PEER_EV_RX_ASSOC_UPDATE_REQ: LOG_CP_PEER(cp_peer, LOGL_ERROR, "PFCP Association Update Request is not implemented\n"); break; default: OSMO_ASSERT(false); } } static void pfcp_cp_peer_associated_onleave(struct osmo_fsm_inst *fi, uint32_t next_state) { 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)); if (cp_peer->assoc_cb) cp_peer->assoc_cb(cp_peer, false); } static void pfcp_cp_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct osmo_pfcp_cp_peer *cp_peer = fi->priv; LOG_CP_PEER(cp_peer, LOGL_ERROR, "PFCP graceful release is not implemented\n"); } static void pfcp_cp_peer_graceful_release_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct osmo_pfcp_cp_peer *cp_peer = fi->priv; LOG_CP_PEER(cp_peer, LOGL_ERROR, "PFCP graceful release is not implemented\n"); } static void pfcp_cp_peer_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct osmo_pfcp_cp_peer *cp_peer = fi->priv; if (!osmo_use_count_total(&cp_peer->use_count)) osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } static void pfcp_cp_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case PFCP_CP_PEER_EV_USE_COUNT_ZERO: switch (fi->state) { default: /* still busy, ignore. */ return; case PFCP_CP_PEER_ST_WAIT_USE_COUNT: /* Waiting for deallocation; now there are no more users, deallocate. */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); return; } default: OSMO_ASSERT(false); } } #define S(x) (1 << (x)) static const struct osmo_fsm_state pfcp_cp_peer_fsm_states[] = { [PFCP_CP_PEER_ST_DISABLED] = { .name = "disabled", .out_state_mask = 0 | S(PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP) | S(PFCP_CP_PEER_ST_WAIT_USE_COUNT) , }, [PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP] = { .name = "wait_assoc_setup_resp", .in_event_mask = 0 | S(PFCP_CP_PEER_EV_RX_ASSOC_SETUP_RESP) , .out_state_mask = 0 | S(PFCP_CP_PEER_ST_ASSOCIATED) | S(PFCP_CP_PEER_ST_WAIT_RETRY) | S(PFCP_CP_PEER_ST_WAIT_USE_COUNT) , .onenter = pfcp_cp_peer_wait_assoc_setup_resp_onenter, .action = pfcp_cp_peer_wait_assoc_setup_resp_action, }, [PFCP_CP_PEER_ST_WAIT_RETRY] = { .name = "wait_retry", .out_state_mask = 0 | S(PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP) | S(PFCP_CP_PEER_ST_WAIT_USE_COUNT) , }, [PFCP_CP_PEER_ST_ASSOCIATED] = { .name = "associated", .in_event_mask = 0 | S(PFCP_CP_PEER_EV_RX_ASSOC_UPDATE_REQ) , .out_state_mask = 0 | S(PFCP_CP_PEER_ST_WAIT_ASSOC_SETUP_RESP) | S(PFCP_CP_PEER_ST_GRACEFUL_RELEASE) | S(PFCP_CP_PEER_ST_WAIT_USE_COUNT) , .onenter = pfcp_cp_peer_associated_onenter, .action = pfcp_cp_peer_associated_action, .onleave = pfcp_cp_peer_associated_onleave, }, [PFCP_CP_PEER_ST_GRACEFUL_RELEASE] = { .name = "graceful_release", .in_event_mask = 0 , .out_state_mask = 0 | S(PFCP_CP_PEER_ST_WAIT_RETRY) | S(PFCP_CP_PEER_ST_WAIT_USE_COUNT) , .onenter = pfcp_cp_peer_graceful_release_onenter, .action = pfcp_cp_peer_graceful_release_action, }, [PFCP_CP_PEER_ST_WAIT_USE_COUNT] = { .name = "wait_use_count", .in_event_mask = 0 | S(PFCP_CP_PEER_EV_USE_COUNT_ZERO) , .onenter = pfcp_cp_peer_wait_use_count_onenter, }, }; static struct osmo_fsm pfcp_cp_peer_fsm = { .name = "pfcp_cp_peer", .states = pfcp_cp_peer_fsm_states, .num_states = ARRAY_SIZE(pfcp_cp_peer_fsm_states), .log_subsys = DLPFCP, .event_names = pfcp_cp_peer_fsm_event_names, .timer_cb = pfcp_cp_peer_fsm_timer_cb, .allstate_action = pfcp_cp_peer_allstate_action, .allstate_event_mask = 0 | S(PFCP_CP_PEER_EV_USE_COUNT_ZERO) , }; static __attribute__((constructor)) void pfcp_cp_peer_fsm_register(void) { OSMO_ASSERT(osmo_fsm_register(&pfcp_cp_peer_fsm) == 0); } bool osmo_pfcp_cp_peer_is_associated(const struct osmo_pfcp_cp_peer *cp_peer) { return cp_peer && cp_peer->fi->state == PFCP_CP_PEER_ST_ASSOCIATED; } void osmo_pfcp_cp_peer_set_msg_ctx(struct osmo_pfcp_cp_peer *cp_peer, struct osmo_pfcp_msg *m) { if (m->ctx.peer_fi) return; m->ctx.peer_fi = cp_peer->fi; m->ctx.peer_use_count = &cp_peer->use_count; m->ctx.peer_use_token = (m->rx ? "PFCPrx" : "PFCPtx"); OSMO_ASSERT(osmo_use_count_get_put(m->ctx.peer_use_count, m->ctx.peer_use_token, 1) == 0); } /* Allocate a new PFCP request message to be sent to cp_peer->remote_addr. */ struct osmo_pfcp_msg *osmo_pfcp_cp_peer_new_req(struct osmo_pfcp_cp_peer *cp_peer, enum osmo_pfcp_message_type msg_type) { struct osmo_pfcp_msg *m; m = osmo_pfcp_msg_alloc_tx_req(cp_peer->ep, &cp_peer->remote_addr, msg_type); OSMO_ASSERT(m); osmo_pfcp_cp_peer_set_msg_ctx(cp_peer, m); return m; } /* Allocate a new PFCP response message to be sent to cp_peer->remote_addr. */ struct osmo_pfcp_msg *osmo_pfcp_cp_peer_new_resp(struct osmo_pfcp_cp_peer *cp_peer, const struct osmo_pfcp_msg *in_reply_to, enum osmo_pfcp_message_type msg_type) { struct osmo_pfcp_msg *m; m = osmo_pfcp_msg_alloc_tx_resp(cp_peer->ep, in_reply_to, msg_type); OSMO_ASSERT(m); osmo_pfcp_cp_peer_set_msg_ctx(cp_peer, m); return m; }