add ps_rab_ass FSM to map GTP via UPF

Related: SYS#5895
Depends: If80c35c6a942bf9593781b5a6bc28ba37323ce5e (libosmo-pfcp)
Change-Id: Ic9bc30f322c4c6c6e82462d1da50cb15b336c63a
This commit is contained in:
Neels Hofmeyr 2022-04-02 02:11:40 +02:00 committed by laforge
parent b7ff03e5be
commit 1496498713
20 changed files with 1964 additions and 9 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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;
};

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
#pragma once
struct hnb_gw;
int hnbgw_pfcp_init(struct hnb_gw *hnb_gw);

View File

@ -0,0 +1,14 @@
#pragma once
#include <osmocom/ranap/ranap_ies_defs.h>
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);

View File

@ -0,0 +1,102 @@
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/use_count.h>
#include <osmocom/pfcp/pfcp_msg.h>
/* 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);

View File

@ -3,4 +3,5 @@
#include <osmocom/core/tdef.h>
extern struct osmo_tdef mgw_fsm_T_defs[];
extern struct osmo_tdef ps_T_defs[];
extern struct osmo_tdef_group hnbgw_tdef_group[];

View File

@ -8,5 +8,6 @@ enum osmo_iuh_vty_node {
IUCS_NODE,
IUPS_NODE,
MGCP_NODE,
PFCP_NODE,
};

View File

@ -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)

View File

@ -28,6 +28,7 @@
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
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);

View File

@ -61,10 +61,13 @@
#include <osmocom/sigtran/protocol/m3ua.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/pfcp/pfcp_proto.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_hnbap.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/hnbgw_pfcp.h>
#include <osmocom/hnbgw/context_map.h>
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) {

View File

@ -29,12 +29,15 @@
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/ranap/RANAP_ProcedureCode.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_ran.h>
@ -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);
}

148
src/osmo-hnbgw/hnbgw_pfcp.c Normal file
View File

@ -0,0 +1,148 @@
/* PFCP link to UPF for osmo-hnbgw */
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/ps_rab_fsm.h>
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;
}

View File

@ -39,6 +39,7 @@
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbap/HNBAP_CN-DomainIndicator.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#include <osmocom/ranap/RANAP_ProcedureCode.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_cn.h>
@ -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);
}

View File

@ -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);
}

View File

@ -0,0 +1,686 @@
/* Handle RANAP PS RAB Assignment */
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <errno.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/prim.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/byteswap.h>
#include <arpa/inet.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/tdef.h>
#include <osmocom/ranap/ranap_common.h>
#include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/iu_helpers.h>
#include <osmocom/pfcp/pfcp_msg.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/ranap_rab_ass.h>
#include <osmocom/hnbgw/ps_rab_fsm.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/tdefs.h>
#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);
}

819
src/osmo-hnbgw/ps_rab_fsm.c Normal file
View File

@ -0,0 +1,819 @@
/* Handle PFCP communication with the UPF for a single RAB. */
/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <errno.h>
#include <osmocom/core/tdef.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_cp_peer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/ps_rab_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
#define LOG_PS_RAB(RAB, LOGL, FMT, ARGS...) \
LOGPFSML((RAB) ? (RAB)->fi : NULL, LOGL, FMT, ##ARGS)
enum ps_rab_state {
PS_RAB_ST_RX_CORE_REMOTE_F_TEID,
PS_RAB_ST_WAIT_PFCP_EST_RESP,
PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID,
PS_RAB_ST_WAIT_PFCP_MOD_RESP,
PS_RAB_ST_ESTABLISHED,
PS_RAB_ST_WAIT_PFCP_DEL_RESP,
PS_RAB_ST_WAIT_USE_COUNT,
};
enum ps_rab_event {
PS_RAB_EV_PFCP_EST_RESP,
PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID,
PS_RAB_EV_PFCP_MOD_RESP,
PS_RAB_EV_PFCP_DEL_RESP,
PS_RAB_EV_USE_COUNT_ZERO,
};
static const struct value_string ps_rab_fsm_event_names[] = {
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_EST_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_RX_ACCESS_REMOTE_F_TEID),
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_MOD_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_PFCP_DEL_RESP),
OSMO_VALUE_STRING(PS_RAB_EV_USE_COUNT_ZERO),
{}
};
struct osmo_tdef_state_timeout ps_rab_fsm_timeouts[32] = {
/* PS_RAB_ST_WAIT_PFCP_EST_RESP is terminated by PFCP timeouts via resp_cb() */
/* PS_RAB_ST_WAIT_ACCESS_REMOTE_F_TEID is terminated by ps_rab_ass_fsm */
/* PS_RAB_ST_WAIT_PFCP_MOD_RESP is terminated by PFCP timeouts via resp_cb() */
/* PS_RAB_ST_WAIT_PFCP_DEL_RESP is terminated by PFCP timeouts via resp_cb() */
};
enum pdr_far_id {
ID_CORE_TO_ACCESS = 1,
ID_ACCESS_TO_CORE = 2,
};
#define ps_rab_fsm_state_chg(state) \
osmo_tdef_fsm_inst_state_chg(fi, state, ps_rab_fsm_timeouts, ps_T_defs, -1)
#define PS_RAB_USE_ACTIVE "active"
static struct osmo_fsm ps_rab_fsm;
static int ps_rab_fsm_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line);
static struct ps_rab *ps_rab_alloc(struct hnbgw_context_map *map, uint8_t rab_id)
{
struct osmo_fsm_inst *fi;
struct ps_rab *rab;
/* Allocate with the global hnb_gw, so that we can gracefully handle PFCP release even if a hnb_ctx gets
* deallocated. */
fi = osmo_fsm_inst_alloc(&ps_rab_fsm, map->hnb_ctx->gw, NULL, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u-RAB-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id,
rab_id);
rab = talloc(fi, struct ps_rab);
OSMO_ASSERT(rab);
*rab = (struct ps_rab){
.fi = fi,
.hnb_gw = map->hnb_ctx->gw,
.map = map,
.rab_id = rab_id,
.use_count = {
.talloc_object = rab,
.use_cb = ps_rab_fsm_use_cb,
},
};
fi->priv = rab;
osmo_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);
}

View File

@ -15,6 +15,7 @@
*/
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/pfcp/pfcp_endpoint.h>
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" },
{ }
};