neels has uploaded this change for review.

View Change

add ps_rab_ass FSM to map GTP via UPF

Related: SYS#5895
Change-Id: Ic9bc30f322c4c6c6e82462d1da50cb15b336c63a
---
M configure.ac
M contrib/jenkins.sh
M include/osmocom/hnbgw/Makefile.am
M include/osmocom/hnbgw/context_map.h
M include/osmocom/hnbgw/hnbgw.h
A include/osmocom/hnbgw/hnbgw_pfcp.h
A include/osmocom/hnbgw/ps_rab_ass_fsm.h
A include/osmocom/hnbgw/ps_rab_fsm.h
M include/osmocom/hnbgw/tdefs.h
M include/osmocom/hnbgw/vty.h
M src/osmo-hnbgw/Makefile.am
M src/osmo-hnbgw/context_map.c
M src/osmo-hnbgw/hnbgw.c
M src/osmo-hnbgw/hnbgw_cn.c
A src/osmo-hnbgw/hnbgw_pfcp.c
M src/osmo-hnbgw/hnbgw_rua.c
M src/osmo-hnbgw/hnbgw_vty.c
A src/osmo-hnbgw/ps_rab_ass_fsm.c
A src/osmo-hnbgw/ps_rab_fsm.c
M src/osmo-hnbgw/tdefs.c
20 files changed, 1,947 insertions(+), 7 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-hnbgw refs/changes/16/28816/1
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(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 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 @@
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..afaeaf2 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,17 @@

/* 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 each Response gets one ps_rab_ass FSM. Each such message can contain any number and any combination of
+ * RAB IDs. The state of each RAB 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..8976604 100644
--- a/include/osmocom/hnbgw/hnbgw.h
+++ b/include/osmocom/hnbgw/hnbgw.h
@@ -19,8 +19,8 @@
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 @@
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 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,8 @@
int hnbgw_vty_go_parent(struct vty *vty);

bool hnbgw_requires_empty_sccp_cr(struct hnb_gw *gw, unsigned int ranap_msg_len);
+
+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 <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);
diff --git a/include/osmocom/hnbgw/ps_rab_fsm.h b/include/osmocom/hnbgw/ps_rab_fsm.h
new file mode 100644
index 0000000..f16a5a5
--- /dev/null
+++ b/include/osmocom/hnbgw/ps_rab_fsm.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/use_count.h>
+#include <osmocom/pfcp/pfcp_msg.h>
+
+struct addr_teid {
+ bool present;
+ struct osmo_sockaddr addr;
+ uint32_t teid;
+};
+
+/* Two of this make up a GTP mapping, one for the Access (HNB) side, one for the Core (CN) side.
+ *
+ * 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 encoded the address in x213_nsap */
+ bool use_x213_nsap;
+};
+
+struct ps_rab {
+ 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. */
+ 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(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 <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[];
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 @@
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 @@
$(LIBOSMORANAP_CFLAGS) \
$(LIBOSMOHNBAP_CFLAGS) \
$(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(LIBOSMOGTLV_CFLAGS) \
+ $(LIBOSMOPFCP_CFLAGS) \
$(NULL)

AM_LDFLAGS = \
@@ -35,11 +37,14 @@
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 @@
$(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 <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 @@
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 @@

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 @@
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 @@
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..89c1e37 100644
--- a/src/osmo-hnbgw/hnbgw.c
+++ b/src/osmo-hnbgw/hnbgw.c
@@ -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 @@
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;
}

@@ -222,6 +227,8 @@

void ue_context_free(struct ue_context *ue)
{
+ LOGP(DHNBAP, LOGL_INFO, "deallocating UE context: id 0x%x, imsi %s, tmsi 0x%x\n",
+ ue->context_id, ue->imsi ? : "-", ue->tmsi);
llist_del(&ue->list);
talloc_free(ue);
}
@@ -344,6 +351,7 @@

osmo_stream_srv_destroy(ctx->conn);

+ LOGP(DMAIN, LOGL_INFO, "Deallocating hnb_context\n");
talloc_free(ctx);
}

@@ -713,6 +721,9 @@
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 <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 @@
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 @@
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));

@@ -385,6 +390,37 @@
}

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

return rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id,
diff --git a/src/osmo-hnbgw/hnbgw_pfcp.c b/src/osmo-hnbgw/hnbgw_pfcp.c
new file mode 100644
index 0000000..a3fe0df
--- /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 <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;
+
+ LOGP(DLPFCP, LOGL_DEBUG, "%p cfg: pfcp remote-addr %s\n", hnb_gw, hnb_gw->config.pfcp.remote_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;
+ }
+ 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..404c11d 100644
--- a/src/osmo-hnbgw/hnbgw_rua.c
+++ b/src/osmo-hnbgw/hnbgw_rua.c
@@ -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,20 @@

/* 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);
+ } else {
+ /* 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..05a30d9 100644
--- a/src/osmo-hnbgw/hnbgw_vty.c
+++ b/src/osmo-hnbgw/hnbgw_vty.c
@@ -362,6 +362,47 @@
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 @@
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.remote_addr)
+ vty_out(vty, " remote-addr %s%s", g_hnb_gw->config.pfcp.remote_addr, 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);
+
+ return CMD_SUCCESS;
+}
+
void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx)
{
g_hnb_gw = gw;
@@ -463,6 +515,12 @@
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_remote_addr_cmd);
+ install_element(PFCP_NODE, &cfg_pfcp_local_addr_cmd);
+ install_element(PFCP_NODE, &cfg_pfcp_local_port_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..c1b8880
--- /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 <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 RTP 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..9f0be5f
--- /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 <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(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 = "PS_RAB_ST_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 = "PS_RAB_ST_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 <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 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" },
{ }
};

To view, visit change 28816. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: osmo-hnbgw
Gerrit-Branch: master
Gerrit-Change-Id: Ic9bc30f322c4c6c6e82462d1da50cb15b336c63a
Gerrit-Change-Number: 28816
Gerrit-PatchSet: 1
Gerrit-Owner: neels <nhofmeyr@sysmocom.de>
Gerrit-MessageType: newchange