osmo-hnbgw/src/osmo-hnbgw/ps_rab_fsm.c

820 lines
26 KiB
C

/* Handle PFCP communication with the UPF for a single RAB. */
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* 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 <errno.h>
#include <osmocom/core/tdef.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/ps_rab_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#define LOG_PS_RAB(RAB, LOGL, FMT, ARGS...) \
LOGPFSML((RAB) ? (RAB)->fi : NULL, LOGL, FMT, ##ARGS)
enum ps_rab_state {
PS_RAB_ST_RX_CORE_REMOTE_F_TEID,
PS_RAB_ST_WAIT_PFCP_EST_RESP,
PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID,
PS_RAB_ST_WAIT_PFCP_MOD_RESP,
PS_RAB_ST_ESTABLISHED,
PS_RAB_ST_WAIT_PFCP_DEL_RESP,
PS_RAB_ST_WAIT_USE_COUNT,
};
enum ps_rab_event {
PS_RAB_EV_PFCP_EST_RESP,
PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID,
PS_RAB_EV_PFCP_MOD_RESP,
PS_RAB_EV_PFCP_DEL_RESP,
PS_RAB_EV_USE_COUNT_ZERO,
};
static const struct value_string ps_rab_fsm_event_names[] = {
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_EST_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID),
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_MOD_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_DEL_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_USE_COUNT_ZERO),
{}
};
struct osmo_tdef_state_timeout ps_rab_fsm_timeouts[32] = {
/* PS_RAB_ST_WAIT_PFCP_EST_RESP is terminated by PFCP timeouts via resp_cb() */
/* PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID is terminated by ps_rab_ass_fsm */
/* PS_RAB_ST_WAIT_PFCP_MOD_RESP is terminated by PFCP timeouts via resp_cb() */
/* PS_RAB_ST_WAIT_PFCP_DEL_RESP is terminated by PFCP timeouts via resp_cb() */
};
enum pdr_far_id {
ID_CORE_TO_ACCESS = 1,
ID_ACCESS_TO_CORE = 2,
};
#define ps_rab_fsm_state_chg(state) \
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_fsm_timeouts, ps_T_defs, -1)
#define PS_RAB_USE_ACTIVE "active"
static struct osmo_fsm ps_rab_fsm;
static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line);
static struct ps_rab *ps_rab_alloc(struct hnbgw_context_map *map, uint8_t rab_id)
{
struct osmo_fsm_inst *fi;
struct ps_rab *rab;
/* Allocate with the global hnb_gw, so that we can gracefully handle PFCP release even if a hnb_ctx gets
* deallocated. */
fi = osmo_fsm_inst_alloc(&ps_rab_fsm, map->hnb_ctx->gw, NULL, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u-RAB-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id,
rab_id);
rab = talloc(fi, struct ps_rab);
OSMO_ASSERT(rab);
*rab = (struct ps_rab){
.fi = fi,
.hnb_gw = map->hnb_ctx->gw,
.map = map,
.rab_id = rab_id,
.use_count = {
.talloc_object = rab,
.use_cb = ps_rab_fsm_use_cb,
},
};
fi->priv = rab;
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, 1) == 0);
llist_add_tail(&rab->entry, &map->ps_rabs);
return rab;
}
/* Iterate all ps_rab instances of all context maps and return the one matching the given SEID.
* If is_cp_seid == true, match seid with rab->cp_seid (e.g. for received PFCP messages).
* Otherwise match seid with rab->up_f_seid.seid (e.g. for sent PFCP messages). */
struct ps_rab *ps_rab_find_by_seid(struct hnb_gw *hnb_gw, uint64_t seid, bool is_cp_seid)
{
struct hnb_context *hnb;
llist_for_each_entry(hnb, &hnb_gw->hnb_list, list) {
struct hnbgw_context_map *map;
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
struct ps_rab *rab;
llist_for_each_entry(rab, &map->ps_rabs, entry) {
uint64_t rab_seid = is_cp_seid ? rab->cp_seid : rab->up_f_seid.seid;
if (rab_seid == seid)
return rab;
}
}
}
return NULL;
}
void ps_rab_pfcp_set_msg_ctx(struct ps_rab *rab, struct osmo_pfcp_msg *m)
{
if (m->ctx.session_fi)
return;
m->ctx.session_fi = rab->fi;
m->ctx.session_use_count = &rab->use_count;
m->ctx.session_use_token = "PFCP_MSG";
OSMO_ASSERT(osmo_use_count_get_put(m->ctx.session_use_count, m->ctx.session_use_token, 1) == 0);
}
static struct osmo_pfcp_msg *ps_rab_new_pfcp_msg_req(struct ps_rab *rab, enum osmo_pfcp_message_type msg_type)
{
struct hnb_gw *hnb_gw = rab->hnb_gw;
struct osmo_pfcp_msg *m = osmo_pfcp_cp_peer_new_req(hnb_gw->pfcp.cp_peer, msg_type);
m->h.seid_present = true;
m->h.seid = rab->up_f_seid.seid;
ps_rab_pfcp_set_msg_ctx(rab, m);
return m;
}
struct ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id)
{
struct ps_rab *rab;
llist_for_each_entry(rab, &map->ps_rabs, entry) {
if (rab->rab_id != rab_id)
continue;
return rab;
}
return NULL;
}
bool ps_rab_is_established(const struct ps_rab *rab)
{
return rab && rab->fi->state == PS_RAB_ST_ESTABLISHED;
}
void ps_rab_failure(struct ps_rab *rab)
{
if (rab->req_fi)
osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
if (rab->resp_fi)
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_FAIL, rab);
ps_rab_release(rab);
}
struct ps_rab *ps_rab_start(struct hnbgw_context_map *map, uint8_t rab_id,
const struct addr_teid *core_f_teid, bool use_x213_nsap,
struct osmo_fsm_inst *req_fi)
{
struct osmo_fsm_inst *fi;
struct ps_rab *rab;
rab = ps_rab_alloc(map, rab_id);
fi = rab->fi;
rab->req_fi = req_fi;
rab->core.remote = *core_f_teid;
rab->core.use_x213_nsap = use_x213_nsap;
/* Got the RAB's Core side GTP info. Route the GTP for via the local UPF.
* Establish a PFCP session with the UPF: tell it about the Core side GTP endpoint and request local F-TEIDs. */
if (ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_EST_RESP)) {
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
return NULL;
}
return rab;
}
/* Add two PDR and two FAR to the PFCP Session Establishment Request message, according to the information found in rab.
*/
static int rab_to_pfcp_session_est_req(struct osmo_pfcp_msg_session_est_req *ser, struct ps_rab *rab)
{
if (ser->create_pdr_count + 2 > ARRAY_SIZE(ser->create_pdr)
|| ser->create_far_count + 2 > ARRAY_SIZE(ser->create_far)) {
LOG_PS_RAB(rab, LOGL_ERROR, "insufficient space for Create PDR / Create FAR IEs\n");
return -1;
}
/* Core to Access:
* - UPF should return an F-TEID for the PDR, to be forwarded back to Core later in
* RANAP RAB Assignment Response.
* - we don't know the Access side GTP address yet, so set FAR to DROP.
*/
ser->create_pdr[ser->create_pdr_count] = (struct osmo_pfcp_ie_create_pdr){
.pdr_id = ID_CORE_TO_ACCESS,
.precedence = 255,
.pdi = {
.source_iface = OSMO_PFCP_SOURCE_IFACE_CORE,
.local_f_teid_present = true,
.local_f_teid = {
.choose_flag = true,
.choose = {
.ipv4_addr = true,
},
},
},
.outer_header_removal_present = true,
.outer_header_removal = {
.desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4,
},
.far_id_present = true,
.far_id = ID_CORE_TO_ACCESS,
};
ser->create_pdr_count++;
ser->create_far[ser->create_far_count] = (struct osmo_pfcp_ie_create_far){
.far_id = ID_CORE_TO_ACCESS,
};
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].apply_action.bits,
OSMO_PFCP_APPLY_ACTION_DROP, true);
ser->create_far_count++;
/* Access to Core:
* - UPF should return an F-TEID for the PDR, to be forwarded to Access in the modified
* RANAP RAB Assignment Request.
* - we already know the Core's GTP endpoint F-TEID, so fully set up this FAR.
*/
ser->create_pdr[ser->create_pdr_count] = (struct osmo_pfcp_ie_create_pdr){
.pdr_id = ID_ACCESS_TO_CORE,
.precedence = 255,
.pdi = {
.source_iface = OSMO_PFCP_SOURCE_IFACE_ACCESS,
.local_f_teid_present = true,
.local_f_teid = {
.choose_flag = true,
.choose = {
.ipv4_addr = true,
},
},
},
.outer_header_removal_present = true,
.outer_header_removal = {
.desc = OSMO_PFCP_OUTER_HEADER_REMOVAL_GTP_U_UDP_IPV4,
},
.far_id_present = true,
.far_id = ID_ACCESS_TO_CORE,
};
ser->create_pdr_count++;
ser->create_far[ser->create_far_count] = (struct osmo_pfcp_ie_create_far){
.far_id = ID_ACCESS_TO_CORE,
.forw_params_present = true,
.forw_params = {
.destination_iface = OSMO_PFCP_DEST_IFACE_CORE,
.outer_header_creation_present = true,
.outer_header_creation = {
.teid_present = true,
.teid = rab->core.remote.teid,
.ip_addr.v4_present = true,
.ip_addr.v4 = rab->core.remote.addr,
},
},
};
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].forw_params.outer_header_creation.desc_bits,
OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
osmo_pfcp_bits_set(ser->create_far[ser->create_far_count].apply_action.bits,
OSMO_PFCP_APPLY_ACTION_FORW, true);
ser->create_far_count++;
return 0;
}
static int on_pfcp_est_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
static void ps_rab_fsm_wait_pfcp_est_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ps_rab *rab = fi->priv;
struct hnb_gw *hnb_gw = rab->hnb_gw;
struct osmo_pfcp_msg *m;
struct osmo_pfcp_ie_f_seid cp_f_seid;
struct osmo_pfcp_msg_session_est_req *ser;
/* So far we have the rab->core.remote information. Send that to the UPF.
* Also request all local GTP endpoints from UPF (rab->{core,access}.local) */
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_EST_REQ);
/* Send UP-SEID as zero, the UPF has yet to assign a SEID for itself remotely */
m->h.seid = 0;
/* Make a new CP-SEID, our local reference for the PFCP session. */
rab->cp_seid = osmo_pfcp_next_seid(&hnb_gw->pfcp.cp_peer->next_seid_state);
cp_f_seid = (struct osmo_pfcp_ie_f_seid){
.seid = rab->cp_seid,
};
osmo_pfcp_ip_addrs_set(&cp_f_seid.ip_addr, &osmo_pfcp_endpoint_get_cfg(hnb_gw->pfcp.ep)->local_addr);
m->ies.session_est_req = (struct osmo_pfcp_msg_session_est_req){
.node_id = m->ies.session_est_req.node_id,
.cp_f_seid_present = true,
.cp_f_seid = cp_f_seid,
};
ser = &m->ies.session_est_req;
/* Create PDR+FAR pairs */
if (rab_to_pfcp_session_est_req(ser, rab)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to compose PFCP message\n");
osmo_pfcp_msg_free(m);
ps_rab_failure(rab);
return;
}
/* Send PFCP Session Establishment Request to UPF, wait for response. */
m->ctx.resp_cb = on_pfcp_est_resp;
if (osmo_pfcp_endpoint_tx(hnb_gw->pfcp.ep, m)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
ps_rab_failure(rab);
}
}
static int on_pfcp_est_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
{
struct ps_rab *rab = req->ctx.session_fi->priv;
/* Send as FSM event to ensure this step is currently allowed */
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_EST_RESP, rx_resp);
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
* here already. */
return 0;
}
static void ps_rab_rx_pfcp_est_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx);
static void ps_rab_fsm_wait_pfcp_est_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_PFCP_EST_RESP:
ps_rab_rx_pfcp_est_resp(fi, data);
break;
default:
OSMO_ASSERT(false);
}
}
/* Look for dst->local.pdr_id in ser->created_pdr[], and copy the GTP endpoint info to dst->local.addr_teid, if found. */
static int get_local_f_teid_from_created_pdr(struct half_gtp_map *dst, struct osmo_pfcp_msg_session_est_resp *ser,
uint8_t pdr_id)
{
int i;
for (i = 0; i < ser->created_pdr_count; i++) {
struct osmo_pfcp_ie_created_pdr *cpdr = &ser->created_pdr[i];
if (cpdr->pdr_id != pdr_id)
continue;
if (!cpdr->local_f_teid_present)
continue;
if (cpdr->local_f_teid.choose_flag)
continue;
if (!cpdr->local_f_teid.fixed.ip_addr.v4_present)
continue;
dst->local.addr = cpdr->local_f_teid.fixed.ip_addr.v4;
dst->local.teid = cpdr->local_f_teid.fixed.teid;
dst->local.present = true;
return 0;
}
return -1;
}
static void ps_rab_rx_pfcp_est_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx)
{
struct ps_rab *rab = fi->priv;
enum osmo_pfcp_cause *cause;
struct osmo_pfcp_msg_session_est_resp *ser;
if (!rx) {
/* This happens when no response has arrived after all PFCP timeouts and retransmissions. */
LOG_PS_RAB(rab, LOGL_ERROR, "No response to PFCP Session Establishment Request\n");
goto pfcp_session_est_failed;
}
ser = &rx->ies.session_est_resp;
cause = osmo_pfcp_msg_cause(rx);
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) {
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Establishment Response was not successful: %s\n",
cause ? osmo_pfcp_cause_str(*cause) : "NULL");
goto pfcp_session_est_failed;
}
/* Get the UPF's SEID for future messages for this PFCP session */
if (!ser->up_f_seid_present) {
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Establishment Response lacks a UP F-SEID\n");
goto pfcp_session_est_failed;
}
rab->up_f_seid = ser->up_f_seid;
if (rab->release_requested) {
/* The UE conn or the entire HNB has released while we were waiting for a PFCP response. Now that there
* is a remote SEID, we can finally delete the session that we asked for earlier. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_DEL_RESP);
return;
}
/* Get the UPF's local F-TEIDs for both Core and Access */
if (get_local_f_teid_from_created_pdr(&rab->core, ser, ID_CORE_TO_ACCESS)
|| get_local_f_teid_from_created_pdr(&rab->access, ser, ID_ACCESS_TO_CORE)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Missing F-TEID in PFCP Session Establishment Response\n");
ps_rab_failure(rab);
return;
}
if (rab->req_fi)
osmo_fsm_inst_dispatch(rab->req_fi, PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX, rab);
/* The RAB Assignment Response will yield the hNodeB's F-TEID, i.e. the F-TEID we are supposed to send to Access
* in outgoing GTP packets. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID);
return;
pfcp_session_est_failed:
if (rab->release_requested) {
/* the RAB was released and we were waiting for some PFCP responsewhile waiting for a response, and now
* we know that no session has been created. No PFCP left, deallocate. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
}
ps_rab_failure(rab);
}
int ps_rab_rx_access_remote_f_teid(struct hnbgw_context_map *map, uint8_t rab_id,
const struct ps_rab_rx_args *args)
{
int rc;
struct ps_rab *rab = ps_rab_get(map, rab_id);
if (!rab) {
LOG_MAP(map, DLPFCP, LOGL_ERROR, "There is no RAB with id %u\n", rab_id);
return -ENOENT;
}
/* Dispatch as event to make sure this is currently allowed */
rc = osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID, (void *)args);
if (rc)
return rc;
return 0;
}
static void ps_rab_fsm_wait_access_remote_f_teid(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ps_rab *rab = fi->priv;
const struct ps_rab_rx_args *args;
switch (event) {
case PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID:
args = data;
rab->resp_fi = args->notify_fi;
rab->access.use_x213_nsap = args->use_x213_nsap;
rab->access.remote = args->f_teid;
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_MOD_RESP);
return;
default:
OSMO_ASSERT(false);
}
}
/* Add an Update FAR to the PFCP Session Modification Request message, updating a remote F-TEID. */
static int rab_to_pfcp_session_mod_req_upd_far(struct osmo_pfcp_msg_session_mod_req *smr,
uint32_t far_id, const struct addr_teid *remote_f_teid)
{
if (smr->upd_far_count + 1 > ARRAY_SIZE(smr->upd_far))
return -1;
smr->upd_far[smr->upd_far_count] = (struct osmo_pfcp_ie_upd_far){
.far_id = far_id,
.apply_action_present = true,
/* apply_action.bits set below */
.upd_forw_params_present = true,
.upd_forw_params = {
.outer_header_creation_present = true,
.outer_header_creation = {
/* desc_bits set below */
.teid_present = true,
.teid = remote_f_teid->teid,
.ip_addr.v4_present = true,
.ip_addr.v4 = remote_f_teid->addr,
},
},
};
osmo_pfcp_bits_set(smr->upd_far[smr->upd_far_count].apply_action.bits,
OSMO_PFCP_APPLY_ACTION_FORW, true);
osmo_pfcp_bits_set(smr->upd_far[smr->upd_far_count].upd_forw_params.outer_header_creation.desc_bits,
OSMO_PFCP_OUTER_HEADER_CREATION_GTP_U_UDP_IPV4, true);
smr->upd_far_count++;
return 0;
}
static int on_pfcp_mod_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
static void ps_rab_fsm_wait_pfcp_mod_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
/* We have been given the Access side's remote F-TEID, now in rab->access.remote, and we need to tell the UPF
* about it. */
struct ps_rab *rab = fi->priv;
struct hnb_gw *hnb_gw = rab->hnb_gw;
struct osmo_pfcp_msg *m;
if (!(rab->up_f_seid.ip_addr.v4_present /* || rab->up_f_seid.ip_addr.v6_present */)) {
LOG_PS_RAB(rab, LOGL_ERROR, "no valid PFCP session\n");
ps_rab_failure(rab);
return;
}
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_MOD_REQ);
if (rab_to_pfcp_session_mod_req_upd_far(&m->ies.session_mod_req, ID_ACCESS_TO_CORE, &rab->access.remote)) {
LOG_PS_RAB(rab, LOGL_ERROR, "error composing Update FAR IE in PFCP msg\n");
ps_rab_failure(rab);
return;
}
m->ctx.resp_cb = on_pfcp_mod_resp;
if (osmo_pfcp_endpoint_tx(hnb_gw->pfcp.ep, m)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
ps_rab_failure(rab);
}
}
static int on_pfcp_mod_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
{
struct ps_rab *rab = req->ctx.session_fi->priv;
/* Send as FSM event to ensure this step is currently allowed */
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_MOD_RESP, rx_resp);
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
* here already. */
return 0;
}
static void ps_rab_rx_pfcp_mod_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx);
static void ps_rab_fsm_wait_pfcp_mod_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_PFCP_MOD_RESP:
ps_rab_rx_pfcp_mod_resp(fi, data);
return;
default:
OSMO_ASSERT(false);
}
}
static void ps_rab_rx_pfcp_mod_resp(struct osmo_fsm_inst *fi, struct osmo_pfcp_msg *rx)
{
struct ps_rab *rab = fi->priv;
enum osmo_pfcp_cause *cause;
if (!rx) {
LOG_PS_RAB(rab, LOGL_ERROR, "No response to PFCP Session Modification Request\n");
ps_rab_failure(rab);
return;
}
cause = osmo_pfcp_msg_cause(rx);
if (!cause || *cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) {
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Modification Response was not successful: %s\n",
cause ? osmo_pfcp_cause_str(*cause) : "NULL");
ps_rab_failure(rab);
return;
}
/* This RAB is now complete. Everything went as expected, now we can forward the RAB Assignment Response to the
* CN. */
ps_rab_fsm_state_chg(PS_RAB_ST_ESTABLISHED);
}
static void ps_rab_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ps_rab *rab = fi->priv;
if (rab->resp_fi)
osmo_fsm_inst_dispatch(rab->resp_fi, PS_RAB_ASS_EV_RAB_ESTABLISHED, rab);
}
static int on_pfcp_del_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg);
static void ps_rab_fsm_wait_pfcp_del_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
/* If a PFCP session has been established, send a Session Deletion Request and wait for the response.
* If no session is established, just terminate. */
struct ps_rab *rab = fi->priv;
struct hnb_gw *hnb_gw = rab->hnb_gw;
struct osmo_pfcp_msg *m;
if (!(rab->up_f_seid.ip_addr.v4_present /* || rab->up_f_seid.ip_addr.v6_present */)) {
/* There is no valid PFCP session, so no need to send a Session Deletion Request */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
}
m = ps_rab_new_pfcp_msg_req(rab, OSMO_PFCP_MSGT_SESSION_DEL_REQ);
m->ctx.resp_cb = on_pfcp_del_resp;
if (osmo_pfcp_endpoint_tx(hnb_gw->pfcp.ep, m)) {
LOG_PS_RAB(rab, LOGL_ERROR, "Failed to send PFCP message\n");
ps_rab_failure(rab);
}
}
static int on_pfcp_del_resp(struct osmo_pfcp_msg *req, struct osmo_pfcp_msg *rx_resp, const char *errmsg)
{
struct ps_rab *rab = req->ctx.session_fi->priv;
if (errmsg)
LOG_PS_RAB(rab, LOGL_ERROR, "PFCP Session Deletion Response: %s\n", errmsg);
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_PFCP_DEL_RESP, rx_resp);
/* By returning 0 here, the rx_resp message is not dispatched "again" to pfcp_ep->rx_msg(). We've handled it
* here already. */
return 0;
}
static void ps_rab_fsm_wait_pfcp_del_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_PFCP_DEL_RESP:
/* All done, terminate. Even if the Session Deletion failed, there's nothing we can do about it. */
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
default:
OSMO_ASSERT(false);
}
}
static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
{
struct ps_rab *rab = e->use_count->talloc_object;
if (!osmo_use_count_total(&rab->use_count))
osmo_fsm_inst_dispatch(rab->fi, PS_RAB_EV_USE_COUNT_ZERO, NULL);
return 0;
}
static void ps_rab_fsm_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ps_rab *rab = fi->priv;
OSMO_ASSERT(osmo_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, -1) == 0);
}
static void ps_rab_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case PS_RAB_EV_USE_COUNT_ZERO:
if (fi->state == PS_RAB_ST_WAIT_USE_COUNT)
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
/* else, ignore. */
return;
default:
OSMO_ASSERT(false);
}
}
static void ps_rab_forget_map(struct ps_rab *rab)
{
if (rab->map)
llist_del(&rab->entry);
rab->map = NULL;
}
static void ps_rab_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct ps_rab *rab = fi->priv;
ps_rab_forget_map(rab);
}
void ps_rab_release(struct ps_rab *rab)
{
struct osmo_fsm_inst *fi = rab->fi;
ps_rab_forget_map(rab);
switch (fi->state) {
case PS_RAB_ST_RX_CORE_REMOTE_F_TEID:
/* No session requested yet. Nothing to be deleted. */
LOG_PS_RAB(rab, LOGL_NOTICE, "RAB release before PFCP Session Establishment Request, terminating\n");
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_USE_COUNT);
return;
case PS_RAB_ST_WAIT_PFCP_EST_RESP:
/* Session was requested via PFCP, but we only know the SEID to send in a deletion when the PFCP Session
* Establishment Response arrives. */
rab->release_requested = true;
LOG_PS_RAB(rab, LOGL_ERROR, "RAB release while waiting for PFCP Session Establishment Response\n");
return;
default:
/* Session has been established (and we know the SEID). Initiate deletion. */
LOG_PS_RAB(rab, LOGL_INFO, "RAB release, deleting PFCP session\n");
ps_rab_fsm_state_chg(PS_RAB_ST_WAIT_PFCP_DEL_RESP);
return;
case PS_RAB_ST_WAIT_PFCP_DEL_RESP:
/* Already requested a PFCP Session Deletion. Nothing else to do, wait for the Deletion Response (or
* timeout). */
LOG_PS_RAB(rab, LOGL_INFO, "RAB release while waiting for PFCP Session Deletion Response\n");
return;
case PS_RAB_ST_WAIT_USE_COUNT:
/* Already released, just wait for the last users (queued PFCP messages) to expire. */
LOG_PS_RAB(rab, LOGL_INFO, "RAB release, already waiting for deallocation\n");
return;
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state ps_rab_fsm_states[] = {
[PS_RAB_ST_RX_CORE_REMOTE_F_TEID] = {
.name = "RX_CORE_REMOTE_F_TEID",
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_PFCP_EST_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_PFCP_EST_RESP] = {
.name = "WAIT_PFCP_EST_RESP",
.onenter = ps_rab_fsm_wait_pfcp_est_resp_onenter,
.action = ps_rab_fsm_wait_pfcp_est_resp,
.in_event_mask = 0
| S(PS_RAB_EV_PFCP_EST_RESP)
,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID] = {
.name = "WAIT_ACCESS_REMOTE_F_TEID",
.action = ps_rab_fsm_wait_access_remote_f_teid,
.in_event_mask = 0
| S(PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID)
,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_PFCP_MOD_RESP)
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_PFCP_MOD_RESP] = {
.name = "WAIT_PFCP_MOD_RESP",
.onenter = ps_rab_fsm_wait_pfcp_mod_resp_onenter,
.action = ps_rab_fsm_wait_pfcp_mod_resp,
.in_event_mask = 0
| S(PS_RAB_EV_PFCP_MOD_RESP)
,
.out_state_mask = 0
| S(PS_RAB_ST_ESTABLISHED)
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_ESTABLISHED] = {
.name = "ESTABLISHED",
.onenter = ps_rab_fsm_established_onenter,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_PFCP_DEL_RESP)
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_PFCP_DEL_RESP] = {
.name = "WAIT_PFCP_DEL_RESP",
.onenter = ps_rab_fsm_wait_pfcp_del_resp_onenter,
.action = ps_rab_fsm_wait_pfcp_del_resp,
.in_event_mask = 0
| S(PS_RAB_EV_PFCP_DEL_RESP)
,
.out_state_mask = 0
| S(PS_RAB_ST_WAIT_USE_COUNT)
,
},
[PS_RAB_ST_WAIT_USE_COUNT] = {
.name = "WAIT_USE_COUNT",
.onenter = ps_rab_fsm_wait_use_count_onenter,
.in_event_mask = 0
| S(PS_RAB_EV_USE_COUNT_ZERO)
,
},
};
static struct osmo_fsm ps_rab_fsm = {
.name = "ps_rab",
.states = ps_rab_fsm_states,
.num_states = ARRAY_SIZE(ps_rab_fsm_states),
.log_subsys = DLPFCP,
.event_names = ps_rab_fsm_event_names,
.cleanup = ps_rab_fsm_cleanup,
.allstate_event_mask = S(PS_RAB_EV_USE_COUNT_ZERO),
.allstate_action = ps_rab_fsm_allstate_action,
};
static __attribute__((constructor)) void ps_rab_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&ps_rab_fsm) == 0);
}