/* * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH * All Rights Reserved. * * Author: Neels Janosch Hofmeyr * * 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 . * */ #include #include #include #include #include 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_alloc(void *ctx, const struct osmo_sockaddr *local_addr) { 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); return up_ep; } int up_endpoint_bind(struct up_endpoint *up_ep) { OSMO_ASSERT(up_ep); OSMO_ASSERT(up_ep->pfcp_ep); return osmo_pfcp_endpoint_bind(up_ep->pfcp_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; } uint64_t up_endpoint_next_up_seid(struct up_endpoint *ep) { uint64_t sanity; for (sanity = 2342; sanity; sanity--) { uint64_t next_seid = osmo_pfcp_next_seid(&ep->next_up_seid_state); if (up_endpoint_find_session(ep, next_seid)) continue; return next_seid; } 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; }