osmo-upf/src/osmo-upf/up_endpoint.c

328 lines
10 KiB
C

/*
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved.
*
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/pfcp/pfcp_endpoint.h>
#include <osmocom/pfcp/pfcp_msg.h>
#include <osmocom/upf/up_endpoint.h>
#include <osmocom/upf/up_peer.h>
#include <osmocom/upf/up_session.h>
static void up_endpoint_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
{
struct up_endpoint *up_ep = osmo_pfcp_endpoint_get_cfg(ep)->priv;
struct up_peer *peer;
/* If this is a response to an earlier request, just take the msg context from the request message. */
if (req) {
if (!m->ctx.peer_fi && req->ctx.peer_fi)
up_peer_set_msg_ctx(req->ctx.peer_fi->priv, m);
if (!m->ctx.session_fi && req->ctx.session_fi)
up_session_set_msg_ctx(req->ctx.session_fi->priv, m);
}
/* From the remote address, find the matching peer instance */
if (!m->ctx.peer_fi) {
peer = up_peer_find(up_ep, &m->remote_addr);
if (peer) {
up_peer_set_msg_ctx(peer, m);
}
} else {
peer = m->ctx.peer_fi->priv;
}
/* Find a session, if the header is parsed yet and contains a SEID */
if (peer && !m->ctx.session_fi && m->h.seid_present) {
struct up_session *session;
session = up_session_find_by_up_seid(peer, m->h.seid);
if (session) {
up_session_set_msg_ctx(session, m);
}
}
}
static void up_ep_rx_not_impl_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m,
enum osmo_pfcp_message_type resp_msgt, enum osmo_pfcp_cause cause)
{
struct osmo_pfcp_msg *tx;
enum osmo_pfcp_cause *tx_cause;
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "message type not implemented\n");
tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, resp_msgt);
tx_cause = osmo_pfcp_msg_cause(tx);
if (tx_cause)
*tx_cause = cause;
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
}
static void up_ep_rx_pfd_mgmt_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_PFD_MGMT_RESP, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
}
static void up_ep_rx_assoc_setup_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
struct up_peer *peer = m->ctx.peer_fi ? m->ctx.peer_fi->priv : NULL;
if (!peer) {
peer = up_peer_find_or_add(up_ep, &m->remote_addr);
OSMO_ASSERT(peer);
}
osmo_fsm_inst_dispatch(peer->fi, UP_PEER_EV_RX_ASSOC_SETUP_REQ, (void *)m);
}
static void up_ep_rx_assoc_upd_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
if (!m->ctx.peer_fi) {
struct osmo_pfcp_msg *tx;
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot update association\n");
tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP);
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
return;
}
osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_UPD_REQ, (void *)m);
}
static void up_ep_rx_assoc_rel_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
if (!m->ctx.peer_fi) {
struct osmo_pfcp_msg *tx;
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated. Sending ACK response anyway\n");
tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP);
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
return;
}
osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_REL_REQ, (void *)m);
}
static void up_ep_rx_node_report_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_NODE_REPORT_RESP, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
}
static void up_ep_rx_session_set_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_SESSION_SET_DEL_RESP,
OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
}
static void up_ep_rx_session_est_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
if (!m->ctx.peer_fi) {
struct osmo_pfcp_msg *tx;
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot establish session\n");
tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_EST_RESP);
tx->ies.session_est_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
return;
}
osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_SESSION_EST_REQ, (void *)m);
}
static void up_ep_rx_session_mod_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
if (!m->ctx.session_fi) {
/* Session not found. */
struct osmo_pfcp_msg *tx;
tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_MOD_RESP);
if (!m->ctx.peer_fi) {
/* Not even the peer is associated. */
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot modify session\n");
tx->ies.session_mod_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
} else {
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR,
"No established session with SEID=0x%"PRIx64", cannot modify\n",
m->h.seid);
tx->ies.session_mod_resp.cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND;
}
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
return;
}
osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_MOD_REQ, (void *)m);
}
static void up_ep_rx_session_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
if (!m->ctx.session_fi) {
/* Session not found. */
struct osmo_pfcp_msg *tx;
tx = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_DEL_RESP);
if (!m->ctx.peer_fi) {
/* Not even the peer is associated. */
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot delete session\n");
tx->ies.session_del_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
} else {
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR,
"No established session with SEID=0x%"PRIx64", cannot delete\n",
m->h.seid);
tx->ies.session_del_resp.cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND;
}
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
return;
}
osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_DEL_REQ, (void *)m);
}
static void up_ep_rx_session_rep_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
{
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_SESSION_REP_RESP, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
}
static void up_endpoint_rx_cb(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
{
struct up_endpoint *up_ep = osmo_pfcp_endpoint_get_priv(ep);
switch (m->h.message_type) {
case OSMO_PFCP_MSGT_PFD_MGMT_REQ:
up_ep_rx_pfd_mgmt_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_ASSOC_SETUP_REQ:
up_ep_rx_assoc_setup_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_ASSOC_UPDATE_REQ:
up_ep_rx_assoc_upd_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_ASSOC_RELEASE_REQ:
up_ep_rx_assoc_rel_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_NODE_REPORT_REQ:
up_ep_rx_node_report_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_SET_DEL_REQ:
up_ep_rx_session_set_del_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_EST_REQ:
up_ep_rx_session_est_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_MOD_REQ:
up_ep_rx_session_mod_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_DEL_REQ:
up_ep_rx_session_del_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_SESSION_REP_REQ:
up_ep_rx_session_rep_req(up_ep, m);
return;
case OSMO_PFCP_MSGT_HEARTBEAT_REQ:
case OSMO_PFCP_MSGT_HEARTBEAT_RESP:
/* Heartbeat is already handled in osmo_pfcp_endpoint_handle_rx() in pfcp_endpoint.c. The heartbeat
* messages are also dispatched here, to the rx_cb, "on informtional basis", nothing needs to happen
* here. */
return;
default:
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Unknown message type\n");
return;
}
}
struct up_endpoint *up_endpoint_init(void *ctx, const struct osmo_sockaddr *local_addr)
{
int rc;
struct osmo_pfcp_endpoint_cfg cfg;
struct up_endpoint *up_ep;
up_ep = talloc_zero(ctx, struct up_endpoint);
INIT_LLIST_HEAD(&up_ep->peers);
cfg = (struct osmo_pfcp_endpoint_cfg){
.local_addr = *local_addr,
.set_msg_ctx_cb = up_endpoint_set_msg_ctx,
.rx_msg_cb = up_endpoint_rx_cb,
.priv = up_ep,
};
osmo_pfcp_ie_node_id_from_osmo_sockaddr(&cfg.local_node_id, local_addr);
up_ep->pfcp_ep = osmo_pfcp_endpoint_create(up_ep, &cfg);
OSMO_ASSERT(up_ep->pfcp_ep);
rc = osmo_pfcp_endpoint_bind(up_ep->pfcp_ep);
if (rc) {
talloc_free(up_ep);
return NULL;
}
return up_ep;
}
static struct up_session *up_endpoint_find_session(struct up_endpoint *ep, uint64_t up_seid)
{
struct up_peer *peer;
llist_for_each_entry(peer, &ep->peers, entry) {
struct up_session *session = up_session_find_by_up_seid(peer, up_seid);
if (session)
return session;
}
return NULL;
}
static struct up_session *up_endpoint_find_session_by_local_teid(struct up_endpoint *ep, uint32_t teid)
{
struct up_peer *peer;
llist_for_each_entry(peer, &ep->peers, entry) {
struct up_session *session = up_session_find_by_local_teid(peer, teid);
if (session)
return session;
}
return NULL;
}
uint64_t up_endpoint_next_seid(struct up_endpoint *ep)
{
uint64_t sanity;
for (sanity = 2342; sanity; sanity--) {
uint64_t next_seid = osmo_pfcp_next_seid(&ep->next_seid_state);
if (up_endpoint_find_session(ep, next_seid))
continue;
return next_seid;
}
return 0;
}
static uint32_t up_endpoint_inc_teid(struct up_endpoint *ep)
{
ep->next_teid_state++;
if (!ep->next_teid_state)
ep->next_teid_state++;
return ep->next_teid_state;
}
uint32_t up_endpoint_next_teid(struct up_endpoint *ep)
{
uint32_t sanity;
for (sanity = 2342; sanity; sanity--) {
uint32_t next_teid = up_endpoint_inc_teid(ep);
if (up_endpoint_find_session_by_local_teid(ep, next_teid))
continue;
return next_teid;
}
return 0;
}
void up_endpoint_free(struct up_endpoint **_ep)
{
struct up_peer *peer;
struct up_endpoint *ep = *_ep;
while ((peer = llist_first_entry_or_null(&ep->peers, struct up_peer, entry)))
up_peer_free(peer);
osmo_pfcp_endpoint_free(&ep->pfcp_ep);
*_ep = NULL;
}