/* Handle PFCP communication with the UPF for a single RAB. */ /* (C) 2022 by sysmocom s.f.m.c. GmbH * All Rights Reserved * * Author: Neels Janosch Hofmeyr * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License for more details. */ #include #include #include #include #include #include #include #include #include #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, hnbgw_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, g_hnbgw, 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, .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(uint64_t seid, bool is_cp_seid) { struct hnb_context *hnb; llist_for_each_entry(hnb, &g_hnbgw->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 osmo_pfcp_msg *m = osmo_pfcp_cp_peer_new_req(g_hnbgw->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; } #define set_netinst(NETINST_MEMBER, STRING) do { \ if ((STRING) && *(STRING)) { \ NETINST_MEMBER##_present = true; \ OSMO_STRLCPY_ARRAY(NETINST_MEMBER.str, STRING); \ } \ } while (0) /* 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, }; set_netinst(ser->create_pdr[ser->create_pdr_count].pdi.network_inst, g_hnbgw->config.pfcp.netinst.core); 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, }; set_netinst(ser->create_pdr[ser->create_pdr_count].pdi.network_inst, g_hnbgw->config.pfcp.netinst.access); 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); set_netinst(ser->create_far[ser->create_far_count].forw_params.network_inst, g_hnbgw->config.pfcp.netinst.core); 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 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_cp_peer_next_seid(g_hnbgw->pfcp.cp_peer); 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(g_hnbgw->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(g_hnbgw->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, const char *far_netinst) { 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); set_netinst(smr->upd_far[smr->upd_far_count].upd_forw_params.network_inst, far_netinst); 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. This affects the Core to Access direction: now we know where to forward payloads coming from Core. */ struct ps_rab *rab = fi->priv; 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_CORE_TO_ACCESS, &rab->access.remote, g_hnbgw->config.pfcp.netinst.access)) { 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(g_hnbgw->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 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(g_hnbgw->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) { /* remove from map->ps_rabs */ 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); }