From 1496498713fc172674665570dfc4fb99dfb114dd Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Sat, 2 Apr 2022 02:11:40 +0200 Subject: [PATCH] add ps_rab_ass FSM to map GTP via UPF Related: SYS#5895 Depends: If80c35c6a942bf9593781b5a6bc28ba37323ce5e (libosmo-pfcp) Change-Id: Ic9bc30f322c4c6c6e82462d1da50cb15b336c63a --- configure.ac | 2 + contrib/jenkins.sh | 1 + include/osmocom/hnbgw/Makefile.am | 6 +- include/osmocom/hnbgw/context_map.h | 21 + include/osmocom/hnbgw/hnbgw.h | 25 +- include/osmocom/hnbgw/hnbgw_pfcp.h | 5 + include/osmocom/hnbgw/ps_rab_ass_fsm.h | 14 + include/osmocom/hnbgw/ps_rab_fsm.h | 102 +++ include/osmocom/hnbgw/tdefs.h | 1 + include/osmocom/hnbgw/vty.h | 1 + src/osmo-hnbgw/Makefile.am | 7 + src/osmo-hnbgw/context_map.c | 8 + src/osmo-hnbgw/hnbgw.c | 8 + src/osmo-hnbgw/hnbgw_cn.c | 38 +- src/osmo-hnbgw/hnbgw_pfcp.c | 148 +++++ src/osmo-hnbgw/hnbgw_rua.c | 11 +- src/osmo-hnbgw/hnbgw_vty.c | 58 ++ src/osmo-hnbgw/ps_rab_ass_fsm.c | 686 +++++++++++++++++++++ src/osmo-hnbgw/ps_rab_fsm.c | 819 +++++++++++++++++++++++++ src/osmo-hnbgw/tdefs.c | 12 +- 20 files changed, 1964 insertions(+), 9 deletions(-) create mode 100644 include/osmocom/hnbgw/hnbgw_pfcp.h create mode 100644 include/osmocom/hnbgw/ps_rab_ass_fsm.h create mode 100644 include/osmocom/hnbgw/ps_rab_fsm.h create mode 100644 src/osmo-hnbgw/hnbgw_pfcp.c create mode 100644 src/osmo-hnbgw/ps_rab_ass_fsm.c create mode 100644 src/osmo-hnbgw/ps_rab_fsm.c diff --git a/configure.ac b/configure.ac index 2405cb0..0f2a7c4 100644 --- a/configure.ac +++ b/configure.ac @@ -61,6 +61,8 @@ PKG_CHECK_MODULES(LIBOSMORUA, libosmo-rua >= 1.3.0) PKG_CHECK_MODULES(LIBOSMORANAP, libosmo-ranap >= 1.3.0) PKG_CHECK_MODULES(LIBOSMOHNBAP, libosmo-hnbap >= 1.3.0) PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.10.0) +PKG_CHECK_MODULES(LIBOSMOGTLV, libosmo-gtlv >= 0.1.0) +PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.1.0) dnl checks for header files AC_HEADER_STDC diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh index b43eac6..98291b4 100755 --- a/contrib/jenkins.sh +++ b/contrib/jenkins.sh @@ -33,6 +33,7 @@ osmo-build-dep.sh libosmocore "" --disable-doxygen osmo-build-dep.sh libosmo-abis osmo-build-dep.sh libosmo-netif osmo-build-dep.sh libosmo-sccp +osmo-build-dep.sh libosmo-pfcp osmo-build-dep.sh libasn1c osmo-build-dep.sh osmo-iuh osmo-build-dep.sh osmo-mgw diff --git a/include/osmocom/hnbgw/Makefile.am b/include/osmocom/hnbgw/Makefile.am index 2a75df8..b6824b3 100644 --- a/include/osmocom/hnbgw/Makefile.am +++ b/include/osmocom/hnbgw/Makefile.am @@ -2,4 +2,8 @@ noinst_HEADERS = \ vty.h \ context_map.h hnbgw.h hnbgw_cn.h \ hnbgw_hnbap.h hnbgw_rua.h hnbgw_ranap.h \ - ranap_rab_ass.h mgw_fsm.h tdefs.h + ranap_rab_ass.h mgw_fsm.h tdefs.h \ + hnbgw_pfcp.h \ + ps_rab_ass_fsm.h \ + ps_rab_fsm.h \ + $(NULL) diff --git a/include/osmocom/hnbgw/context_map.h b/include/osmocom/hnbgw/context_map.h index d6fc2e7..99efe8a 100644 --- a/include/osmocom/hnbgw/context_map.h +++ b/include/osmocom/hnbgw/context_map.h @@ -6,6 +6,13 @@ struct msgb; +#define LOG_MAP(HNB_CTX_MAP, SUBSYS, LEVEL, FMT, ARGS...) \ + LOGHNB((HNB_CTX_MAP) ? (HNB_CTX_MAP)->hnb_ctx : NULL, \ + SUBSYS, LEVEL, "RUA-%u %s: " FMT, \ + (HNB_CTX_MAP) ? (HNB_CTX_MAP)->rua_ctx_id : 0, \ + (HNB_CTX_MAP) ? ((HNB_CTX_MAP)->is_ps ? "PS" : "CS") : "NULL", \ + ##ARGS) \ + enum hnbgw_context_map_state { MAP_S_NULL, MAP_S_ACTIVE, /* currently active map */ @@ -44,6 +51,20 @@ struct hnbgw_context_map { /* FSM instance for the MGW */ struct osmo_fsm_inst *mgw_fi; + + /* FSMs handling RANAP RAB assignments for PS. list of struct ps_rab_ass. For PS RAB Assignment, each Request + * and gets one ps_rab_ass FSM and each Response gets one ps_rab_ass FSM. The reason is that theoretically, each + * such message can contain any number and any combination of RAB IDs, and Request and Response don't + * necessarily match the RAB IDs contained. In practice I only ever see a single RAB matching in Request and + * Response, but we cannot rely on that to always be true. The state of each RAB's PFCP negotiation is kept + * separately in the list hnbgw_context_map.ps_rabs, and as soon as all RABs appearing in a PS RAB Assignment + * message have completed their PFCP setup, we can replace the GTP info for the RAB IDs and forward the RAB + * Assignment Request to HNB / the RAB Assignment Response to CN. */ + struct llist_head ps_rab_ass; + + /* All PS RABs and their GTP tunnel mappings. list of struct ps_rab. Each ps_rab FSM handles the PFCP + * communication for one particular RAB ID. */ + struct llist_head ps_rabs; }; diff --git a/include/osmocom/hnbgw/hnbgw.h b/include/osmocom/hnbgw/hnbgw.h index 76c5b84..af89008 100644 --- a/include/osmocom/hnbgw/hnbgw.h +++ b/include/osmocom/hnbgw/hnbgw.h @@ -19,8 +19,8 @@ enum { DMGW, }; -#define LOGHNB(x, ss, lvl, fmt, args ...) \ - LOGP(ss, lvl, "%s " fmt, hnb_context_name(x), ## args) +#define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \ + LOGP(ss, lvl, "%s " fmt, hnb_context_name(HNB_CTX), ## args) enum hnb_ctrl_node { CTRL_NODE_HNB = _LAST_CTRL_NODE, @@ -136,6 +136,12 @@ struct hnb_gw { bool log_prefix_hnb_id; unsigned int max_sccp_cr_payload_len; struct mgcp_client_conf *mgcp_client; + struct { + char *local_addr; + uint16_t local_port; + char *remote_addr; + uint16_t remote_port; + } pfcp; } config; /*! SCTP listen socket for incoming connections */ struct osmo_stream_srv_link *iuh; @@ -155,6 +161,11 @@ struct hnb_gw { struct osmo_sccp_addr iups_remote_addr; } sccp; struct mgcp_client *mgcp_client; + + struct { + struct osmo_pfcp_endpoint *ep; + struct osmo_pfcp_cp_peer *cp_peer; + } pfcp; }; extern void *talloc_asn1_ctx; @@ -178,3 +189,13 @@ void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx); int hnbgw_vty_go_parent(struct vty *vty); bool hnbgw_requires_empty_sccp_cr(struct hnb_gw *gw, unsigned int ranap_msg_len); + +/* Return true when the user configured GTP mapping to be enabled, by configuring a PFCP link to a UPF. + * Return false when the user configured to skip GTP mapping and RANAP PS RAB Requests/Responses should be passed thru + * 1:1. + * GTP mapping means that there are two GTP tunnels, one towards HNB and one towards CN, and we forward payloads between + * the two tunnels, mapping the TEIDs and GTP addresses. */ +static inline bool hnb_gw_is_gtp_mapping_enabled(const struct hnb_gw *gw) +{ + return gw->config.pfcp.remote_addr != NULL; +} diff --git a/include/osmocom/hnbgw/hnbgw_pfcp.h b/include/osmocom/hnbgw/hnbgw_pfcp.h new file mode 100644 index 0000000..f3f5bb4 --- /dev/null +++ b/include/osmocom/hnbgw/hnbgw_pfcp.h @@ -0,0 +1,5 @@ +#pragma once + +struct hnb_gw; + +int hnbgw_pfcp_init(struct hnb_gw *hnb_gw); diff --git a/include/osmocom/hnbgw/ps_rab_ass_fsm.h b/include/osmocom/hnbgw/ps_rab_ass_fsm.h new file mode 100644 index 0000000..775d73a --- /dev/null +++ b/include/osmocom/hnbgw/ps_rab_ass_fsm.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +enum ps_rab_ass_fsm_event { + PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX, + PS_RAB_ASS_EV_RAB_ASS_RESP, + PS_RAB_ASS_EV_RAB_ESTABLISHED, + PS_RAB_ASS_EV_RAB_FAIL, +}; + +int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message); +int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message); +void hnbgw_gtpmap_release(struct hnbgw_context_map *map); diff --git a/include/osmocom/hnbgw/ps_rab_fsm.h b/include/osmocom/hnbgw/ps_rab_fsm.h new file mode 100644 index 0000000..e52c2ca --- /dev/null +++ b/include/osmocom/hnbgw/ps_rab_fsm.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + +/* A GTP tunnel has two endpoints, each endpoint has an IP address and a Tunnel Endpoint ID. So two struct addr_teid + * identify one GTP tunnel. For GTP mapping between HNB and CN, we have two tunnels, see also struct half_gtp_map. The + * combination of IP address and TEID is also known as F-TEID (fully qualified TEID). */ +struct addr_teid { + bool present; + struct osmo_sockaddr addr; + uint32_t teid; +}; + +/* One half_gtp_map represents one GTP tunnel, either on the HNB side or on the CN side. Two struct half_gtp_map make up + * a GTP mapping between HNB and CN. One half_gtp_map for the Access (HNB) side, one for the Core (CN) side. The PFCP + * PDR (Packet Detection Rule) identifies packets coming in on the GTP tunnel the half_gtp_map represents, while the + * PFCP FAR (Forwarding Action Rule) identifies the GTP destination, i.e. the other side's GTP tunnel. So a + * half_gtp_map.far_id is closely tied to the other half_gtp_map, and makes little sense on its own. + * + * half_gtp_map | half_gtp_map + * Access HNBGW+UPF Core + * remote local | local remote + * -->PDR-FAR-->| + * |<--FAR-PDR<-- + * + * See ps_rab.core, ps_rab.access. + */ +struct half_gtp_map { + /* GTP endpoint, obtained from incoming RAB Assignment Request/Response. + * This is the remote side as seen from the UPF's point of view. + * For example, ps_rab.core.remote is the CN GTP that the RAB Assignment Request told us. + * ps_rab.access.remote is the HNB GTP that RAB Assignment Response told us. */ + struct addr_teid remote; + /* UPF GTP endpoint, obtained from PFCP Session Establishment Response. */ + struct addr_teid local; + /* PFCP Packet Detection Rule id that detects GTP-U packets coming from Core/Access */ + uint16_t pdr_id; + /* PFCP Forward Action Rule id that forwards GTP-U packets to Access/Core */ + uint32_t far_id; + /* Whether the RANAP message this RAB's remote address was obtained from had the address encoded in x213_nsap */ + bool use_x213_nsap; +}; + +/* A PS RAB's PFCP state. For the related RANAP state, see struct ps_rab_ass instead. */ +struct ps_rab { + /* Instance of ps_rab_fsm. */ + struct osmo_fsm_inst *fi; + + /* backpointer */ + struct hnb_gw *hnb_gw; + + /* List entry and backpointer. + * If map == NULL, do not call llist_del(&entry): the hnbgw_context_map may deallocate before the PFCP release + * is complete, in which case it sets map = NULL. */ + struct llist_head entry; + struct hnbgw_context_map *map; + + /* RAB-ID used in RANAP RAB AssignmentRequest and Response messages */ + uint8_t rab_id; + /* Backpointer to the ps_rab_ass_fsm for the RAB Assignment Request from Core that created this RAB. + * There are two separate RAB Assignment FSMs responsible for this RAB, one for the Request message and one for + * the Response message. Each RAB Assignment FSM may be responsible for N other RABs besides this one. */ + struct osmo_fsm_inst *req_fi; + /* Backpointer to the ps_rab_ass_fsm for the RAB Assignment Response from Access that confirmed this RAB. */ + struct osmo_fsm_inst *resp_fi; + + /* PFCP session controlling the GTP mapping for this RAB */ + uint64_t cp_seid; + struct osmo_pfcp_ie_f_seid up_f_seid; + bool release_requested; + + /* 'local' and 'remote' refer to the GTP information from the UPF's point of view: + * HNB UPF CN + * access.remote <---> access.local | core.local <---> core.remote + */ + struct half_gtp_map core; + struct half_gtp_map access; + + struct osmo_use_count use_count; +}; + +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 ps_rab *ps_rab_get(struct hnbgw_context_map *map, uint8_t rab_id); +bool ps_rab_is_established(const struct ps_rab *rab); +void ps_rab_release(struct ps_rab *rab); + +struct ps_rab_rx_args { + struct addr_teid f_teid; + bool use_x213_nsap; + struct osmo_fsm_inst *notify_fi; +}; +int ps_rab_rx_access_remote_f_teid(struct hnbgw_context_map *map, uint8_t rab_id, + const struct ps_rab_rx_args *args); + +struct ps_rab *ps_rab_find_by_seid(struct hnb_gw *hnb_gw, uint64_t seid, bool is_cp_seid); +void ps_rab_pfcp_set_msg_ctx(struct ps_rab *rab, struct osmo_pfcp_msg *m); diff --git a/include/osmocom/hnbgw/tdefs.h b/include/osmocom/hnbgw/tdefs.h index 4f98a36..8ae1c97 100644 --- a/include/osmocom/hnbgw/tdefs.h +++ b/include/osmocom/hnbgw/tdefs.h @@ -3,4 +3,5 @@ #include extern struct osmo_tdef mgw_fsm_T_defs[]; +extern struct osmo_tdef ps_T_defs[]; extern struct osmo_tdef_group hnbgw_tdef_group[]; diff --git a/include/osmocom/hnbgw/vty.h b/include/osmocom/hnbgw/vty.h index 93b3c45..e97a1ad 100644 --- a/include/osmocom/hnbgw/vty.h +++ b/include/osmocom/hnbgw/vty.h @@ -8,5 +8,6 @@ enum osmo_iuh_vty_node { IUCS_NODE, IUPS_NODE, MGCP_NODE, + PFCP_NODE, }; diff --git a/src/osmo-hnbgw/Makefile.am b/src/osmo-hnbgw/Makefile.am index 64d5ccd..4d2403d 100644 --- a/src/osmo-hnbgw/Makefile.am +++ b/src/osmo-hnbgw/Makefile.am @@ -20,6 +20,8 @@ AM_CFLAGS = \ $(LIBOSMORANAP_CFLAGS) \ $(LIBOSMOHNBAP_CFLAGS) \ $(LIBOSMOMGCPCLIENT_CFLAGS) \ + $(LIBOSMOGTLV_CFLAGS) \ + $(LIBOSMOPFCP_CFLAGS) \ $(NULL) AM_LDFLAGS = \ @@ -35,11 +37,14 @@ osmo_hnbgw_SOURCES = \ hnbgw_hnbap.c \ hnbgw_rua.c \ hnbgw_ranap.c \ + hnbgw_pfcp.c \ hnbgw_vty.c \ context_map.c \ hnbgw_cn.c \ ranap_rab_ass.c \ mgw_fsm.c \ + ps_rab_ass_fsm.c \ + ps_rab_fsm.c \ tdefs.c \ $(NULL) @@ -58,4 +63,6 @@ osmo_hnbgw_LDADD = \ $(LIBOSMOMGCPCLIENT_LIBS) \ $(LIBSCTP_LIBS) \ $(LIBOSMOMGCPCLIENT_LIBS) \ + $(LIBOSMOGTLV_LIBS) \ + $(LIBOSMOPFCP_LIBS) \ $(NULL) diff --git a/src/osmo-hnbgw/context_map.c b/src/osmo-hnbgw/context_map.c index 5a7b48b..6bd4f6c 100644 --- a/src/osmo-hnbgw/context_map.c +++ b/src/osmo-hnbgw/context_map.c @@ -28,6 +28,7 @@ #include #include #include +#include const struct value_string hnbgw_context_map_state_names[] = { {MAP_S_NULL , "not-initialized"}, @@ -102,6 +103,8 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id, map->rua_ctx_id = rua_ctx_id; map->is_ps = is_ps; map->scu_conn_id = new_scu_conn_id; + INIT_LLIST_HEAD(&map->ps_rab_ass); + INIT_LLIST_HEAD(&map->ps_rabs); /* put it into both lists */ llist_add_tail(&map->hnb_list, &hnb->map_list); @@ -147,6 +150,8 @@ int context_map_send_cached_msg(struct hnbgw_context_map *map) void context_map_deactivate(struct hnbgw_context_map *map) { + LOG_MAP(map, DMAIN, LOGL_NOTICE, "Deactivating\n"); + /* set the state to reserved. We still show up in the list and * avoid re-allocation of the context-id until we are cleaned up * by the context_map garbage collector timer */ @@ -160,6 +165,8 @@ void context_map_deactivate(struct hnbgw_context_map *map) mgw_fsm_release(map); OSMO_ASSERT(map->mgw_fi == NULL); } + + hnbgw_gtpmap_release(map); } static struct osmo_timer_list context_map_tmr; @@ -181,6 +188,7 @@ static void context_map_tmr_cb(void *data) case MAP_S_RESERVED2: /* second time we see this reserved * entry: remove it */ + LOG_MAP(map, DMAIN, LOGL_NOTICE, "Deallocating\n"); map->state = MAP_S_NULL; llist_del(&map->cn_list); llist_del(&map->hnb_list); diff --git a/src/osmo-hnbgw/hnbgw.c b/src/osmo-hnbgw/hnbgw.c index 04ca34c..8b72a8f 100644 --- a/src/osmo-hnbgw/hnbgw.c +++ b/src/osmo-hnbgw/hnbgw.c @@ -61,10 +61,13 @@ #include #include +#include + #include #include #include #include +#include #include static const char * const osmo_hnbgw_copyright = @@ -101,6 +104,8 @@ static struct hnb_gw *hnb_gw_create(void *ctx) gw->config.mgcp_client = talloc_zero(tall_hnb_ctx, struct mgcp_client_conf); mgcp_client_conf_init(gw->config.mgcp_client); + gw->config.pfcp.remote_port = OSMO_PFCP_PORT; + return gw; } @@ -713,6 +718,9 @@ int main(int argc, char **argv) return -EINVAL; } + /* If UPF is configured, set up PFCP socket and send Association Setup Request to UPF */ + hnbgw_pfcp_init(g_hnb_gw); + if (hnbgw_cmdline_config.daemonize) { rc = osmo_daemonize(); if (rc < 0) { diff --git a/src/osmo-hnbgw/hnbgw_cn.c b/src/osmo-hnbgw/hnbgw_cn.c index 7c1dc9d..75b1c94 100644 --- a/src/osmo-hnbgw/hnbgw_cn.c +++ b/src/osmo-hnbgw/hnbgw_cn.c @@ -29,12 +29,15 @@ #include #include +#include + #include #include #include #include #include #include +#include #include #include #include @@ -353,6 +356,7 @@ static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink, struct hnbgw_context_map *map; ranap_message *message; int rc; + struct hnb_gw *hnb_gw = cnlink->gw; /* Usually connection-oriented data is always passed transparently towards the specific HNB, via a RUA * connection identified by conn_id. An exception is made for RANAP RAB AssignmentRequest and @@ -365,8 +369,9 @@ static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink, return 0; } - /* Intercept RAB Assignment Request, Setup MGW FSM */ + /* Intercept RAB Assignment Request, to map RTP and GTP between access and core */ if (!map->is_ps) { + /* Circuit-Switched. Set up mapping of RTP ports via MGW */ message = talloc_zero(map, ranap_message); rc = ranap_ran_rx_co_decode(map, message, msgb_l2(oph->msg), msgb_l2len(oph->msg)); @@ -384,6 +389,37 @@ static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink, ranap_ran_rx_co_free(message); } + talloc_free(message); + } else { + /* Packet-Switched. Set up mapping of GTP ports via UPF */ + message = talloc_zero(map, ranap_message); + rc = ranap_ran_rx_co_decode(map, message, msgb_l2(oph->msg), msgb_l2len(oph->msg)); + + if (rc == 0) { + switch (message->procedureCode) { + + case RANAP_ProcedureCode_id_RAB_Assignment: + /* If a UPF is configured, handle the RAB Assignment via ps_rab_ass_fsm, and replace the + * GTP F-TEIDs in the RAB Assignment message before passing it on to RUA. */ + if (hnb_gw_is_gtp_mapping_enabled(hnb_gw)) { + LOGP(DMAIN, LOGL_DEBUG, + "RAB Assignment: setting up GTP tunnel mapping via UPF %s\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &hnb_gw->pfcp.cp_peer->remote_addr)); + return hnbgw_gtpmap_rx_rab_ass_req(map, oph, message); + } + /* If no UPF is configured, directly forward the message as-is (no GTP mapping). */ + LOGP(DMAIN, LOGL_DEBUG, "RAB Assignment: no UPF configured, forwarding as-is\n"); + break; + + case RANAP_ProcedureCode_id_Iu_Release: + /* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the + * FSM code. It is just forwarded normally by the rua_tx_dt() call below. */ + hnbgw_gtpmap_release(map); + break; + } + ranap_ran_rx_co_free(message); + } + talloc_free(message); } diff --git a/src/osmo-hnbgw/hnbgw_pfcp.c b/src/osmo-hnbgw/hnbgw_pfcp.c new file mode 100644 index 0000000..6973a1b --- /dev/null +++ b/src/osmo-hnbgw/hnbgw_pfcp.c @@ -0,0 +1,148 @@ +/* PFCP link to UPF for osmo-hnbgw */ +/* (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. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include + +static void pfcp_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req) +{ + struct hnb_gw *hnb_gw = osmo_pfcp_endpoint_get_priv(ep); + + if (!m->ctx.peer_fi) + osmo_pfcp_cp_peer_set_msg_ctx(hnb_gw->pfcp.cp_peer, m); + + /* If this is a response to an earlier request, just take the msg context from the request message. + * In osmo-hnbgw, a session_fi always points at a ps_rab FSM. */ + if (!m->ctx.session_fi && req && req->ctx.session_fi) + ps_rab_pfcp_set_msg_ctx(req->ctx.session_fi->priv, m); + + /* Otherwise iterate all PS RABs in all hnb contexts matching on the SEID. This rarely happens at all: for tx, + * ps_rab_new_pfcp_msg_tx() already sets the msg ctx, and for rx, we only expect to receive PFCP Responses, + * which are handled above. The only time this will happen is when the UPF shuts down and sends a Deletion. */ + if (!m->ctx.session_fi && m->h.seid_present && m->h.seid != 0) { + struct ps_rab *rab = ps_rab_find_by_seid(hnb_gw, m->h.seid, m->rx); + if (rab) + ps_rab_pfcp_set_msg_ctx(rab, m); + } +} + +static void pfcp_rx_msg(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req) +{ + switch (m->h.message_type) { + + /* We only expect responses to requests. Those are handled by osmo_pfcp_msg.ctx.resp_cb. */ + + /* TODO: handle graceful shutdown from UPF (Session Modification? Deletion?) */ + + default: + LOGP(DLPFCP, LOGL_ERROR, "rx unexpected PFCP message: %s\n", + osmo_pfcp_message_type_str(m->h.message_type)); + return; + } +} + +int hnbgw_pfcp_init(struct hnb_gw *hnb_gw) +{ + struct osmo_pfcp_endpoint_cfg cfg; + struct osmo_pfcp_endpoint *ep; + struct osmo_sockaddr_str local_addr_str; + struct osmo_sockaddr_str upf_addr_str; + struct osmo_sockaddr upf_addr; + + if (!hnb_gw_is_gtp_mapping_enabled(hnb_gw)) { + LOGP(DLPFCP, LOGL_NOTICE, "No UPF configured, NOT setting up PFCP, NOT mapping GTP via UPF\n"); + return 0; + } + LOGP(DLPFCP, LOGL_DEBUG, "%p cfg: pfcp remote-addr %s\n", hnb_gw, hnb_gw->config.pfcp.remote_addr); + + if (!hnb_gw->config.pfcp.local_addr) { + LOGP(DLPFCP, LOGL_ERROR, "Configuration error: missing local PFCP address, required for Node Id\n"); + return -1; + } + + cfg = (struct osmo_pfcp_endpoint_cfg){ + .set_msg_ctx_cb = pfcp_set_msg_ctx, + .rx_msg_cb = pfcp_rx_msg, + .priv = hnb_gw, + }; + + /* Set up PFCP endpoint's local node id from local IP address. Parse address string into local_addr_str... */ + if (osmo_sockaddr_str_from_str(&local_addr_str, hnb_gw->config.pfcp.local_addr, hnb_gw->config.pfcp.local_port)) { + LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n", + osmo_quote_str_c(OTC_SELECT, hnb_gw->config.pfcp.local_addr, -1)); + return -1; + } + /* ...and convert to osmo_sockaddr, write to ep->cfg */ + if (osmo_sockaddr_str_to_sockaddr(&local_addr_str, &cfg.local_addr.u.sas)) { + LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n", + osmo_quote_str_c(OTC_SELECT, hnb_gw->config.pfcp.local_addr, -1)); + return -1; + } + /* also store the local addr as local Node ID */ + if (osmo_pfcp_ie_node_id_from_osmo_sockaddr(&cfg.local_node_id, &cfg.local_addr)) { + LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP local IP: %s\n", + osmo_quote_str_c(OTC_SELECT, hnb_gw->config.pfcp.local_addr, -1)); + return -1; + } + + hnb_gw->pfcp.ep = ep = osmo_pfcp_endpoint_create(hnb_gw, &cfg); + if (!ep) { + LOGP(DLPFCP, LOGL_ERROR, "Failed to allocate PFCP endpoint\n"); + return -1; + } + + /* Set up remote PFCP address to reach UPF at. First parse the string into upf_addr_str. */ + if (osmo_sockaddr_str_from_str(&upf_addr_str, hnb_gw->config.pfcp.remote_addr, hnb_gw->config.pfcp.remote_port)) { + LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP remote IP: %s\n", + osmo_quote_str_c(OTC_SELECT, hnb_gw->config.pfcp.remote_addr, -1)); + return -1; + } + /* then convert upf_addr_str to osmo_sockaddr */ + if (osmo_sockaddr_str_to_sockaddr(&upf_addr_str, &upf_addr.u.sas)) { + LOGP(DLPFCP, LOGL_ERROR, "Error in PFCP remote IP: %s\n", + osmo_quote_str_c(OTC_SELECT, hnb_gw->config.pfcp.remote_addr, -1)); + return -1; + } + + /* Start the socket */ + if (osmo_pfcp_endpoint_bind(ep)) { + LOGP(DLPFCP, LOGL_ERROR, "Cannot bind PFCP endpoint\n"); + return -1; + } + + /* Associate with UPF */ + hnb_gw->pfcp.cp_peer = osmo_pfcp_cp_peer_alloc(hnb_gw, ep, &upf_addr); + if (!hnb_gw->pfcp.cp_peer) { + LOGP(DLPFCP, LOGL_ERROR, "Cannot allocate PFCP CP Peer FSM\n"); + return -1; + } + if (osmo_pfcp_cp_peer_associate(hnb_gw->pfcp.cp_peer)) { + LOGP(DLPFCP, LOGL_ERROR, "Cannot start PFCP CP Peer FSM\n"); + return -1; + } + + return 0; +} diff --git a/src/osmo-hnbgw/hnbgw_rua.c b/src/osmo-hnbgw/hnbgw_rua.c index 56d2724..f3ce5d9 100644 --- a/src/osmo-hnbgw/hnbgw_rua.c +++ b/src/osmo-hnbgw/hnbgw_rua.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -273,15 +274,19 @@ int rua_to_scu(struct hnb_context *hnb, /* If there is data, see if it is a RAB Assignment message where we need to change the user plane information, * for RTP mapping via MGW (soon also GTP mapping via UPF). */ - if (data && len && map && !map->is_ps && !release_context_map) { + if (data && len && map && !release_context_map) { message = talloc_zero(map, ranap_message); rc = ranap_cn_rx_co_decode(map, message, msgb_l2(prim->oph.msg), msgb_l2len(prim->oph.msg)); if (rc == 0) { switch (message->procedureCode) { case RANAP_ProcedureCode_id_RAB_Assignment: - /* mgw_fsm_handle_rab_ass_resp() takes ownership of prim->oph and (ranap) message */ - return mgw_fsm_handle_rab_ass_resp(map, &prim->oph, message); + if (!map->is_ps) { + /* mgw_fsm_handle_rab_ass_resp() takes ownership of prim->oph and (ranap) message */ + return mgw_fsm_handle_rab_ass_resp(map, &prim->oph, message); + } + /* ps_rab_ass_fsm takes ownership of prim->oph and RANAP message */ + return hnbgw_gtpmap_rx_rab_ass_resp(map, &prim->oph, message); } ranap_cn_rx_co_free(message); } diff --git a/src/osmo-hnbgw/hnbgw_vty.c b/src/osmo-hnbgw/hnbgw_vty.c index c263ab3..03f6617 100644 --- a/src/osmo-hnbgw/hnbgw_vty.c +++ b/src/osmo-hnbgw/hnbgw_vty.c @@ -362,6 +362,47 @@ DEFUN(cfg_hnbgw_iups_remote_addr, return CMD_SUCCESS; } +static struct cmd_node pfcp_node = { + PFCP_NODE, + "%s(config-hnbgw-pfcp)# ", + 1, +}; + +DEFUN(cfg_hnbgw_pfcp, cfg_hnbgw_pfcp_cmd, + "pfcp", "Configure PFCP for GTP tunnel mapping") +{ + vty->node = PFCP_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_pfcp_remote_addr, cfg_pfcp_remote_addr_cmd, + "remote-addr IP_ADDR", + "Remote UPF's listen IP address; where to send PFCP requests\n" + "IP address\n") +{ + osmo_talloc_replace_string(g_hnb_gw, &g_hnb_gw->config.pfcp.remote_addr, argv[0]); + LOGP(DLPFCP, LOGL_NOTICE, "%p cfg: pfcp remote-addr %s\n", g_hnb_gw, g_hnb_gw->config.pfcp.remote_addr); + return CMD_SUCCESS; +} + +DEFUN(cfg_pfcp_local_addr, cfg_pfcp_local_addr_cmd, + "local-addr IP_ADDR", + "Local address for PFCP\n" + "IP address\n") +{ + osmo_talloc_replace_string(g_hnb_gw, &g_hnb_gw->config.pfcp.local_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_pfcp_local_port, cfg_pfcp_local_port_cmd, + "local-port <1-65535>", + "Local port for PFCP\n" + "IP port\n") +{ + g_hnb_gw->config.pfcp.local_port = atoi(argv[0]); + return CMD_SUCCESS; +} + static int config_write_hnbgw(struct vty *vty) { vty_out(vty, "hnbgw%s", VTY_NEWLINE); @@ -425,6 +466,17 @@ static int config_write_hnbgw_mgcp(struct vty *vty) return CMD_SUCCESS; } +static int config_write_hnbgw_pfcp(struct vty *vty) +{ + vty_out(vty, " pfcp%s", VTY_NEWLINE); + if (g_hnb_gw->config.pfcp.local_addr) + vty_out(vty, " local-addr %s%s", g_hnb_gw->config.pfcp.local_addr, VTY_NEWLINE); + if (g_hnb_gw->config.pfcp.remote_addr) + vty_out(vty, " remote-addr %s%s", g_hnb_gw->config.pfcp.remote_addr, VTY_NEWLINE); + + return CMD_SUCCESS; +} + void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx) { g_hnb_gw = gw; @@ -463,6 +515,12 @@ void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx) install_element(HNBGW_NODE, &cfg_hnbgw_mgcp_cmd); install_node(&mgcp_node, config_write_hnbgw_mgcp); + install_node(&pfcp_node, config_write_hnbgw_pfcp); + install_element(HNBGW_NODE, &cfg_hnbgw_pfcp_cmd); + install_element(PFCP_NODE, &cfg_pfcp_local_addr_cmd); + install_element(PFCP_NODE, &cfg_pfcp_local_port_cmd); + install_element(PFCP_NODE, &cfg_pfcp_remote_addr_cmd); + mgcp_client_vty_init(tall_hnb_ctx, MGCP_NODE, g_hnb_gw->config.mgcp_client); osmo_tdef_vty_groups_init(HNBGW_NODE, hnbgw_tdef_group); } diff --git a/src/osmo-hnbgw/ps_rab_ass_fsm.c b/src/osmo-hnbgw/ps_rab_ass_fsm.c new file mode 100644 index 0000000..b298e45 --- /dev/null +++ b/src/osmo-hnbgw/ps_rab_ass_fsm.c @@ -0,0 +1,686 @@ +/* Handle RANAP PS RAB Assignment */ +/* (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 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 + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#define PORT_GTP1_U 2152 + +#define LOG_PS_RAB_ASS(RAB_ASS, LOGL, FMT, ARGS...) \ + LOGPFSML((RAB_ASS) ? (RAB_ASS)->fi : NULL, LOGL, FMT, ##ARGS) + +enum ps_rab_ass_fsm_event { + PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX, + PS_RAB_ASS_EV_RAB_ASS_RESP, + PS_RAB_ASS_EV_RAB_ESTABLISHED, + PS_RAB_ASS_EV_RAB_FAIL, +}; + +static const struct value_string ps_rab_ass_fsm_event_names[] = { + OSMO_VALUE_STRING(PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX), + OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_ASS_RESP), + OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_ESTABLISHED), + OSMO_VALUE_STRING(PS_RAB_ASS_EV_RAB_FAIL), + {} +}; + +enum ps_rab_ass_state { + PS_RAB_ASS_ST_RX_RAB_ASS_MSG, + PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS, + PS_RAB_ASS_ST_RX_RAB_ASS_RESP, + PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED, +}; + +/* Represents one RANAP PS RAB Assignment Request and Response dialog. + * There may be any number of PS RAB Assignment Requests, each with any number of RABs being established. We need to + * manage these asynchronously and flexibly: + * - RABs may be assigned in a group and released one by one, or vice versa; + * - we can only forward a RAB Assignment Request / Response when all RABs appearing in it have been set up by the UPF. + * + * This structure manages the RAB Assignment procedures, and the currently set up RABs: + * + * - hnbgw_context_map + * - .ps_rab_ass: list of PS RAB Assignment procedures + * - ps_rab_ass_fsm: one RANAP PS RAB Assignment procedure + * - ... + * - .ps_rabs: list of individual PS RABs + * - ps_rab_fsm: one GTP mapping with PFCP session to the UPF, for a single RAB + * - ... + * + * This ps_rab_ass_fsm lives from a received RAB Assignment Request up to the sent RAB Assignment Response; it + * deallocates when all the RABs have been set up. + * + * The ps_rab_ass_fsm sets up ps_rab_fsm instances, which live longer: up until a RAB or conn release is performed. + */ +struct ps_rab_ass { + struct llist_head entry; + + struct osmo_fsm_inst *fi; + + /* backpointer */ + struct hnbgw_context_map *map; + + ranap_message *ranap_rab_ass_req_message; + + ranap_message *ranap_rab_ass_resp_message; + struct osmo_prim_hdr *ranap_rab_ass_resp_oph; + + /* A RAB Assignment may contain more than one RAB. Each RAB sets up a distinct ps_rab_fsm (aka PFCP session) and + * reports back about local F-TEIDs assigned by the UPF. This gives the nr of RAB events we expect from + * ps_rab_fsms, without iterating the RAB Assignment message every time (minor optimisation). */ + int rabs_count; + int rabs_done_count; +}; + +struct osmo_tdef_state_timeout ps_rab_ass_fsm_timeouts[32] = { + /* PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS is terminated by PFCP timeouts via ps_rab_fsm */ + /* PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED is terminated by PFCP timeouts via ps_rab_fsm */ +}; + +#define ps_rab_ass_fsm_state_chg(state) \ + osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_ass_fsm_timeouts, ps_T_defs, -1) + +static struct osmo_fsm ps_rab_ass_fsm; + +static struct ps_rab_ass *ps_rab_ass_alloc(struct hnbgw_context_map *map) +{ + struct ps_rab_ass *rab_ass; + struct osmo_fsm_inst *fi; + + fi = osmo_fsm_inst_alloc(&ps_rab_ass_fsm, map, map, LOGL_DEBUG, NULL); + OSMO_ASSERT(fi); + osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id); + + rab_ass = talloc(fi, struct ps_rab_ass); + OSMO_ASSERT(rab_ass); + *rab_ass = (struct ps_rab_ass){ + .fi = fi, + .map = map, + }; + fi->priv = rab_ass; + + llist_add_tail(&rab_ass->entry, &map->ps_rab_ass); + return rab_ass; +} + +static void ps_rab_ass_failure(struct ps_rab_ass *rab_ass) +{ + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PS RAB Assignment failed\n"); + + /* TODO: send unsuccessful RAB Assignment Response to Core? */ + /* TODO: remove RAB from Access? */ + + osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +/* Add a single RAB from a RANAP PS RAB Assignment Request's list of RABs */ +static int ps_rab_setup_core_remote(struct ps_rab_ass *rab_ass, RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair) +{ + struct hnbgw_context_map *map = rab_ass->map; + uint8_t rab_id; + struct addr_teid f_teid = {}; + bool use_x213_nsap; + struct ps_rab *rab; + + RANAP_RAB_SetupOrModifyItemFirst_t first; + RANAP_TransportLayerAddress_t *transp_layer_addr; + RANAP_TransportLayerInformation_t *tli; + int rc; + + if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) + return -1; + + /* Extract information about the GTP Core side */ + rc = ranap_decode_rab_setupormodifyitemfirst(&first, + &protocol_ie_field_pair->firstValue); + if (rc < 0) + goto error_exit; + + rab_id = first.rAB_ID.buf[0]; + + /* Decode GTP endpoint IP-Address */ + tli = first.transportLayerInformation; + transp_layer_addr = &tli->transportLayerAddress; + rc = ranap_transp_layer_addr_decode2(&f_teid.addr, &use_x213_nsap, transp_layer_addr); + if (rc < 0) + goto error_exit; + osmo_sockaddr_set_port(&f_teid.addr.u.sa, PORT_GTP1_U); + + /* Decode the GTP remote TEID */ + if (tli->iuTransportAssociation.present != RANAP_IuTransportAssociation_PR_gTP_TEI) { + rc = -1; + goto error_exit; + } + f_teid.teid = osmo_load32be(tli->iuTransportAssociation.choice.gTP_TEI.buf); + f_teid.present = true; + + rab_ass->rabs_count++; + rab = ps_rab_start(map, rab_id, &f_teid, use_x213_nsap, rab_ass->fi); + if (!rab) { + rc = -1; + goto error_exit; + } + rc = 0; + +error_exit: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first); + return rc; +} + +int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message) +{ + RANAP_RAB_AssignmentRequestIEs_t *ies = &message->msg.raB_AssignmentRequestIEs; + int i; + + struct hnb_gw *hnb_gw = map->hnb_ctx->gw; + struct ps_rab_ass *rab_ass; + struct osmo_fsm_inst *fi; + + rab_ass = ps_rab_ass_alloc(map); + rab_ass->ranap_rab_ass_req_message = message; + /* Now rab_ass owns message and will clean it up */ + + if (!osmo_pfcp_cp_peer_is_associated(hnb_gw->pfcp.cp_peer)) { + LOG_MAP(map, DLPFCP, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n"); + goto no_rab; + } + + /* Make sure we indeed deal with a setup-or-modify list */ + if (!(ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_SETUPORMODIFYLIST_PRESENT)) { + LOG_MAP(map, DLPFCP, LOGL_ERROR, "RANAP PS RAB AssignmentRequest lacks setup-or-modify list\n"); + goto no_rab; + } + + /* Multiple RABs may be set up, assemble in list rab_ass->ps_rabs. */ + for (i = 0; i < ies->raB_SetupOrModifyList.list.count; i++) { + RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair; + RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair; + + protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i]; + protocol_ie_field_pair = protocol_ie_container_pair->list.array[0]; + if (!protocol_ie_field_pair) + goto no_rab; + if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) + goto no_rab; + + if (ps_rab_setup_core_remote(rab_ass, protocol_ie_field_pair)) + goto no_rab; + } + + /* Got all RABs' state and their Core side GTP info in map->ps_rabs. For each, a ps_rab_fsm has been started and + * each will call back with PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX or PS_RAB_ASS_EV_RAB_FAIL. */ + fi = rab_ass->fi; + return ps_rab_ass_fsm_state_chg(PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS); + +no_rab: + ps_rab_ass_failure(rab_ass); + return -1; +} + +static void ps_rab_ass_req_check_local_f_teids(struct ps_rab_ass *rab_ass); + +static void ps_rab_ass_fsm_wait_local_f_teids(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ps_rab_ass *rab_ass = fi->priv; + + switch (event) { + case PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX: + rab_ass->rabs_done_count++; + if (rab_ass->rabs_done_count < rab_ass->rabs_count) { + /* some RABs are still pending, postpone going through the message until all are done. */ + return; + } + ps_rab_ass_req_check_local_f_teids(rab_ass); + return; + + case PS_RAB_ASS_EV_RAB_FAIL: + ps_rab_ass_failure(rab_ass); + return; + + default: + OSMO_ASSERT(false); + } +} + +/* See whether all information is in so that we can forward the modified RAB Assignment Request to RUA. */ +static void ps_rab_ass_req_check_local_f_teids(struct ps_rab_ass *rab_ass) +{ + struct ps_rab *rab; + RANAP_RAB_AssignmentRequestIEs_t *ies = &rab_ass->ranap_rab_ass_req_message->msg.raB_AssignmentRequestIEs; + int i; + struct msgb *msg; + + /* Go through all RABs in the RAB Assignment Request message and replace with the F-TEID that the UPF assigned, + * verifying that we indeed have local F-TEIDs for all RABs contained in this message. */ + for (i = 0; i < ies->raB_SetupOrModifyList.list.count; i++) { + RANAP_ProtocolIE_ContainerPair_t *protocol_ie_container_pair; + RANAP_ProtocolIE_FieldPair_t *protocol_ie_field_pair; + RANAP_RAB_SetupOrModifyItemFirst_t first; + uint8_t rab_id; + int rc; + + protocol_ie_container_pair = ies->raB_SetupOrModifyList.list.array[i]; + protocol_ie_field_pair = protocol_ie_container_pair->list.array[0]; + if (!protocol_ie_field_pair) + continue; + if (protocol_ie_field_pair->id != RANAP_ProtocolIE_ID_id_RAB_SetupOrModifyItem) + continue; + + /* Get to the information about the GTP Core side */ + rc = ranap_decode_rab_setupormodifyitemfirst(&first, + &protocol_ie_field_pair->firstValue); + if (rc < 0) + goto continue_cleanloop; + + rab_id = first.rAB_ID.buf[0]; + + /* Find struct ps_rab for this rab_id */ + rab = ps_rab_get(rab_ass->map, rab_id); + if (!rab || !rab->access.local.present) { + /* Not ready to send on the RAB Assignment Request to RUA, a local F-TEID is missing. */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first); + return; + } + + /* Replace GTP endpoint */ + ASN_STRUCT_FREE(asn_DEF_RANAP_TransportLayerInformation, first.transportLayerInformation); + first.transportLayerInformation = ranap_new_transp_info_gtp(&rab->access.local.addr, + rab->access.local.teid, + rab->core.use_x213_nsap); + + /* Reencode to update transport-layer-information */ + rc = ANY_fromType_aper(&protocol_ie_field_pair->firstValue, &asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, + &first); + if (rc < 0) + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB Assignment Request failed\n"); +continue_cleanloop: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifyItemFirst, &first); + } + + /* Send the modified RAB Assignment Request to the hNodeB, wait for the RAB Assignment Response */ + msg = ranap_rab_ass_req_encode(ies); + if (!msg) { + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB Assignment Request failed\n"); + ps_rab_ass_failure(rab_ass); + return; + } + rua_tx_dt(rab_ass->map->hnb_ctx, rab_ass->map->is_ps, rab_ass->map->rua_ctx_id, msg->data, msg->len); + /* The request message has been forwarded. The response will be handled by a new FSM instance. + * We are done. */ + osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +/* Add a single RAB from a RANAP/RUA RAB Assignment Response's list of RABs */ +static int ps_rab_setup_access_remote(struct ps_rab_ass *rab_ass, + RANAP_RAB_SetupOrModifiedItem_t *rab_item) +{ + struct hnbgw_context_map *map = rab_ass->map; + uint8_t rab_id; + int rc; + struct ps_rab_rx_args args = {}; + + rab_id = rab_item->rAB_ID.buf[0]; + + rc = ranap_transp_layer_addr_decode2(&args.f_teid.addr, &args.use_x213_nsap, rab_item->transportLayerAddress); + if (rc < 0) + return rc; + + /* Decode the GTP remote TEID */ + if (!rab_item->iuTransportAssociation + || rab_item->iuTransportAssociation->present != RANAP_IuTransportAssociation_PR_gTP_TEI) + return -1; + args.f_teid.teid = osmo_load32be(rab_item->iuTransportAssociation->choice.gTP_TEI.buf); + args.f_teid.present = true; + + args.notify_fi = rab_ass->fi; + + return ps_rab_rx_access_remote_f_teid(map, rab_id, &args); +} + +int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message) +{ + /* hNodeB responds with its own F-TEIDs. Need to tell the UPF about those to complete the GTP mapping. + * 1. here, extract the F-TEIDs (one per RAB), + * trigger each ps_rab_fsm to do a PFCP Session Modification. + * 2. after all ps_rab_fsms responded with success, insert our Core side local F-TEIDs and send on the RAB + * Assignment Response to IuPS. (We already know the local F-TEIDs assigned by the UPF and could send on the + * RAB Assignment Response immediately, but rather wait for the PFCP mod req to succeed first.) + * + * To wait for all the RABs in this response message to complete, create a *separate* rab_ass_fsm instance from + * the one created for the earlier RAB Assignment Request message. The reason is that technically we cannot + * assume that the request and the response have exactly matching RAB IDs contained in them. + * + * In the vast majority of practical cases, there will be only one RAB Assignment Request message pending, but + * for interop, by treating each RAB on its own and by treating request and response message separately from + * each other, we are able to handle mismatching RAB IDs in request and response messages. + */ + + int rc; + int i; + struct ps_rab_ass *rab_ass; + struct osmo_fsm_inst *fi; + RANAP_RAB_AssignmentResponseIEs_t *ies; + struct hnb_gw *hnb_gw = map->hnb_ctx->gw; + + /* Make sure we indeed deal with a setup-or-modify list */ + ies = &message->msg.raB_AssignmentResponseIEs; + if (!(ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT)) { + LOG_MAP(map, DRUA, LOGL_ERROR, "RANAP PS RAB AssignmentResponse lacks setup-or-modify list\n"); + return -1; + } + + rab_ass = ps_rab_ass_alloc(map); + rab_ass->ranap_rab_ass_resp_message = message; + rab_ass->ranap_rab_ass_resp_oph = oph; + /* Now rab_ass owns message and will clean it up */ + + if (!osmo_pfcp_cp_peer_is_associated(hnb_gw->pfcp.cp_peer)) { + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "PFCP is not associated, cannot set up GTP mapping\n"); + ps_rab_ass_failure(rab_ass); + return -1; + } + + LOG_PS_RAB_ASS(rab_ass, LOGL_NOTICE, "PS RAB-AssignmentResponse received, updating RABs\n"); + + /* Multiple RABs may be set up, bump matching FSMs in list rab_ass->ps_rabs. */ + for (i = 0; i < ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count; i++) { + RANAP_IE_t *list_ie; + RANAP_RAB_SetupOrModifiedItemIEs_t item_ies; + + list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i]; + if (!list_ie) + continue; + + rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies, + &list_ie->value); + if (rc < 0) { + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to decode PS RAB-AssignmentResponse" + " SetupOrModifiedItemIEs with list index %d\n", i); + goto continue_cleanloop; + } + + if (ps_rab_setup_access_remote(rab_ass, &item_ies.raB_SetupOrModifiedItem)) + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to apply PS RAB-AssignmentResponse" + " SetupOrModifiedItemIEs with list index %d\n", i); + rab_ass->rabs_count++; + +continue_cleanloop: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies); + } + + /* Got all RABs' state and updated their Access side GTP info in map->ps_rabs. For each RAB ID, the matching + * ps_rab_fsm has been instructed to tell the UPF about the Access Remote GTP F-TEID. Each will call back with + * PS_RAB_ASS_EV_RAB_ESTABLISHED or PS_RAB_ASS_EV_RAB_FAIL. */ + fi = rab_ass->fi; + return ps_rab_ass_fsm_state_chg(PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED); +} + +static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass); + +static void ps_rab_ass_fsm_wait_rabs_established(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct ps_rab_ass *rab_ass = fi->priv; + + switch (event) { + case PS_RAB_ASS_EV_RAB_ESTABLISHED: + rab_ass->rabs_done_count++; + if (rab_ass->rabs_done_count < rab_ass->rabs_count) { + /* some RABs are still pending, postpone going through the message until all are done. */ + return; + } + /* All RABs have succeeded, ready to forward */ + ps_rab_ass_resp_send_if_ready(rab_ass); + return; + + case PS_RAB_ASS_EV_RAB_FAIL: + ps_rab_ass_failure(rab_ass); + return; + default: + OSMO_ASSERT(false); + } +} + +/* See whether all RABs are done establishing, and replace GTP info in the RAB Assignment Response message, so that we + * can forward the modified RAB Assignment Request to M3UA. */ +static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass) +{ + int i; + int rc; + struct hnbgw_cnlink *cn = rab_ass->map->cn_link; + RANAP_RAB_AssignmentResponseIEs_t *ies = &rab_ass->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs; + + /* Go through all RABs in the RAB Assignment Response message and replace with the F-TEID that the UPF assigned, + * verifying that instructing the UPF has succeeded. */ + for (i = 0; i < ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.count; i++) { + RANAP_IE_t *list_ie; + RANAP_RAB_SetupOrModifiedItemIEs_t item_ies; + RANAP_RAB_SetupOrModifiedItem_t *rab_item; + int rc; + uint8_t rab_id; + uint32_t teid_be; + struct ps_rab *rab; + + list_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[i]; + if (!list_ie) + continue; + + rc = ranap_decode_rab_setupormodifieditemies_fromlist(&item_ies, + &list_ie->value); + if (rc < 0) { + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Failed to decode PS RAB-AssignmentResponse" + " SetupOrModifiedItemIEs with list index %d\n", i); + goto continue_cleanloop; + } + + rab_item = &item_ies.raB_SetupOrModifiedItem; + rab_id = rab_item->rAB_ID.buf[0]; + + /* Find struct ps_rab for this rab_id */ + rab = ps_rab_get(rab_ass->map, rab_id); + if (!ps_rab_is_established(rab)) { + /* Not ready to send on the RAB Assignment Response to M3UA, still waiting for it to be + * established */ + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies); + return; + } + + /* Replace GTP endpoint */ + if (ranap_new_transp_layer_addr(rab_item->transportLayerAddress, &rab->core.local.addr, + rab->access.use_x213_nsap) < 0) { + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies); + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n"); + ps_rab_ass_failure(rab_ass); + return; + } + + LOG_PS_RAB_ASS(rab_ass, LOGL_DEBUG, "Re-encoding RANAP PS RAB-AssignmentResponse: RAB %u:" + " RUA sent F-TEID %s-0x%x; replacing with %s-0x%x\n", + rab_id, + osmo_sockaddr_to_str_c(OTC_SELECT, &rab->access.remote.addr), rab->access.remote.teid, + osmo_sockaddr_to_str_c(OTC_SELECT, &rab->core.local.addr), rab->core.local.teid); + + teid_be = htonl(rab->core.local.teid); + rab_item->iuTransportAssociation->present = RANAP_IuTransportAssociation_PR_gTP_TEI; + OCTET_STRING_fromBuf(&rab_item->iuTransportAssociation->choice.gTP_TEI, + (const char *)&teid_be, sizeof(teid_be)); + + /* Reencode this list item in the RANAP message */ + rc = ANY_fromType_aper(&list_ie->value, &asn_DEF_RANAP_RAB_SetupOrModifiedItem, rab_item); + if (rc < 0) { + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies); + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n"); + ps_rab_ass_failure(rab_ass); + return; + } + +continue_cleanloop: + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies); + } + + /* Replaced all the GTP info, re-encode the message. Since we are replacing data 1:1, taking care to use the + * same IP address encoding, the resulting message size must be identical to the original message size. */ + rc = ranap_rab_ass_resp_encode(msgb_l2(rab_ass->ranap_rab_ass_resp_oph->msg), + msgb_l2len(rab_ass->ranap_rab_ass_resp_oph->msg), ies); + if (rc < 0) { + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n"); + ps_rab_ass_failure(rab_ass); + return; + } + + LOG_PS_RAB_ASS(rab_ass, LOGL_NOTICE, "Sending RANAP PS RAB-AssignmentResponse with mapped GTP info\n"); + rc = osmo_sccp_user_sap_down(cn->sccp_user, rab_ass->ranap_rab_ass_resp_oph); + rab_ass->ranap_rab_ass_resp_oph = NULL; + if (rc < 0) { + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n"); + ps_rab_ass_failure(rab_ass); + } + + /* The request message has been forwarded. We are done. */ + osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +static void ps_rab_ass_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct ps_rab_ass *rab_ass = fi->priv; + struct osmo_scu_prim *scu_prim; + struct msgb *scu_msg; + struct ps_rab *rab; + + if (rab_ass->ranap_rab_ass_req_message) { + ranap_ran_rx_co_free(rab_ass->ranap_rab_ass_req_message); + talloc_free(rab_ass->ranap_rab_ass_req_message); + rab_ass->ranap_rab_ass_req_message = NULL; + } + + if (rab_ass->ranap_rab_ass_resp_message) { + ranap_cn_rx_co_free(rab_ass->ranap_rab_ass_resp_message); + talloc_free(rab_ass->ranap_rab_ass_resp_message); + rab_ass->ranap_rab_ass_resp_message = NULL; + } + + if (rab_ass->ranap_rab_ass_resp_oph) { + scu_prim = (struct osmo_scu_prim *)rab_ass->ranap_rab_ass_resp_oph; + scu_msg = scu_prim->oph.msg; + msgb_free(scu_msg); + rab_ass->ranap_rab_ass_resp_oph = NULL; + } + + llist_for_each_entry(rab, &rab_ass->map->ps_rabs, entry) { + if (rab->req_fi == fi) + rab->req_fi = NULL; + if (rab->resp_fi == fi) + rab->resp_fi = NULL; + } + + llist_del(&rab_ass->entry); +} + +void hnbgw_gtpmap_release(struct hnbgw_context_map *map) +{ + struct ps_rab_ass *rab_ass, *next; + struct ps_rab *rab, *next2; + llist_for_each_entry_safe(rab, next2, &map->ps_rabs, entry) { + ps_rab_release(rab); + } + llist_for_each_entry_safe(rab_ass, next, &map->ps_rab_ass, entry) { + osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL); + } +} + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state ps_rab_ass_fsm_states[] = { + [PS_RAB_ASS_ST_RX_RAB_ASS_MSG] = { + .name = "RX_RAB_ASS_MSG", + .out_state_mask = 0 + | S(PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS) + | S(PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED) + , + }, + [PS_RAB_ASS_ST_WAIT_LOCAL_F_TEIDS] = { + .name = "WAIT_LOCAL_F_TEIDS", + .action = ps_rab_ass_fsm_wait_local_f_teids, + .in_event_mask = 0 + | S(PS_RAB_ASS_EV_LOCAL_F_TEIDS_RX) + | S(PS_RAB_ASS_EV_RAB_FAIL) + , + }, + [PS_RAB_ASS_ST_WAIT_RABS_ESTABLISHED] = { + .name = "WAIT_RABS_ESTABLISHED", + .action = ps_rab_ass_fsm_wait_rabs_established, + .in_event_mask = 0 + | S(PS_RAB_ASS_EV_RAB_ESTABLISHED) + | S(PS_RAB_ASS_EV_RAB_FAIL) + , + }, +}; + +int ps_rab_ass_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct ps_rab_ass *rab_ass = fi->priv; + LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Timeout of " OSMO_T_FMT "\n", OSMO_T_FMT_ARGS(fi->T)); + /* terminate */ + return 1; +} + +static struct osmo_fsm ps_rab_ass_fsm = { + .name = "ps_rab_ass", + .states = ps_rab_ass_fsm_states, + .num_states = ARRAY_SIZE(ps_rab_ass_fsm_states), + .log_subsys = DRANAP, + .event_names = ps_rab_ass_fsm_event_names, + .cleanup = ps_rab_ass_fsm_cleanup, + .timer_cb = ps_rab_ass_fsm_timer_cb, +}; + +static __attribute__((constructor)) void ps_rab_ass_fsm_register(void) +{ + OSMO_ASSERT(osmo_fsm_register(&ps_rab_ass_fsm) == 0); +} diff --git a/src/osmo-hnbgw/ps_rab_fsm.c b/src/osmo-hnbgw/ps_rab_fsm.c new file mode 100644 index 0000000..8578561 --- /dev/null +++ b/src/osmo-hnbgw/ps_rab_fsm.c @@ -0,0 +1,819 @@ +/* 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 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 + +#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, 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_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, 1); + + 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_use_count_get_put(m->ctx.session_use_count, m->ctx.session_use_token, 1); +} + +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_use_count_get_put(&rab->use_count, PS_RAB_USE_ACTIVE, -1); +} + +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); +} diff --git a/src/osmo-hnbgw/tdefs.c b/src/osmo-hnbgw/tdefs.c index 13e04c5..5113dd3 100644 --- a/src/osmo-hnbgw/tdefs.c +++ b/src/osmo-hnbgw/tdefs.c @@ -15,6 +15,7 @@ */ #include +#include struct osmo_tdef mgw_fsm_T_defs[] = { {.T = -1001, .default_val = 5, .desc = "Timeout for HNB side call-leg (to-HNB) creation" }, @@ -25,7 +26,14 @@ struct osmo_tdef mgw_fsm_T_defs[] = { { } }; -struct osmo_tdef_group hnbgw_tdef_group[] = { - {.name = "mgw", .tdefs = mgw_fsm_T_defs, .desc = "MGW (Media Gateway) interface" }, +struct osmo_tdef ps_T_defs[] = { + {.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to PS RAB Assignment Request" }, + { } +}; + +struct osmo_tdef_group hnbgw_tdef_group[] = { + {.name = "mgw", .tdefs = mgw_fsm_T_defs, .desc = "MGW (Media Gateway) interface" }, + {.name = "ps", .tdefs = ps_T_defs, .desc = "timers for Packet Switched domain" }, + {.name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP timers" }, { } };