328 lines
10 KiB
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;
|
|
}
|