osmo-hnbgw/src/osmo-hnbgw/hnbgw_pfcp.c

149 lines
5.5 KiB
C

/* 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;
}