add ps_rab_ass FSM to map GTP via UPF

Related: SYS#5895
Depends: If80c35c6a942bf9593781b5a6bc28ba37323ce5e (libosmo-pfcp)
Change-Id: Ic9bc30f322c4c6c6e82462d1da50cb15b336c63a
changes/16/28816/4
Neels Hofmeyr 8 months ago committed by laforge
parent b7ff03e5be
commit 1496498713
  1. 2
      configure.ac
  2. 1
      contrib/jenkins.sh
  3. 6
      include/osmocom/hnbgw/Makefile.am
  4. 21
      include/osmocom/hnbgw/context_map.h
  5. 25
      include/osmocom/hnbgw/hnbgw.h
  6. 5
      include/osmocom/hnbgw/hnbgw_pfcp.h
  7. 14
      include/osmocom/hnbgw/ps_rab_ass_fsm.h
  8. 102
      include/osmocom/hnbgw/ps_rab_fsm.h
  9. 1
      include/osmocom/hnbgw/tdefs.h
  10. 1
      include/osmocom/hnbgw/vty.h
  11. 7
      src/osmo-hnbgw/Makefile.am
  12. 8
      src/osmo-hnbgw/context_map.c
  13. 8
      src/osmo-hnbgw/hnbgw.c
  14. 38
      src/osmo-hnbgw/hnbgw_cn.c
  15. 148
      src/osmo-hnbgw/hnbgw_pfcp.c
  16. 11
      src/osmo-hnbgw/hnbgw_rua.c
  17. 58
      src/osmo-hnbgw/hnbgw_vty.c
  18. 686
      src/osmo-hnbgw/ps_rab_ass_fsm.c
  19. 819
      src/osmo-hnbgw/ps_rab_fsm.c
  20. 8
      src/osmo-hnbgw/tdefs.c

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

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

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

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

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

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

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

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

@ -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[];

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

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

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

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

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

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

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

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

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