/* * (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 #include static struct pfcp_entity_peer *up_endp_find_msg_entity_peer(const struct up_endpoint *up_ep, struct osmo_pfcp_msg *m) { struct pfcp_node_peer *node_peer; /* Look up over remote PFCP Entity identified by remote IP address and our local PFCP Entity */ llist_for_each_entry(node_peer, &up_ep->pfcp_node_peer_list, entry) { struct pfcp_entity_peer *entity_peer; llist_for_each_entry(entity_peer, &node_peer->entity_list, entry) { if (osmo_sockaddr_cmp(&entity_peer->remote_addr, &m->remote_addr)) continue; return entity_peer; } } return NULL; } 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 pfcp_entity_peer *entity_peer = NULL; /* 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) pfcp_entity_peer_set_msg_ctx(req->ctx.peer_fi->priv, m); if (!m->ctx.session_fi && req->ctx.session_fi) pfcp_entity_peer_set_msg_ctx(req->ctx.session_fi->priv, m); } /* From the Node Id, find the matching PFCP Node */ if (m->ctx.peer_fi) { entity_peer = m->ctx.peer_fi->priv; } else { entity_peer = up_endp_find_msg_entity_peer(up_ep, m); if (entity_peer) pfcp_entity_peer_set_msg_ctx(entity_peer, m); } /* Find a session, if the header is parsed yet and contains a SEID */ if (entity_peer && !m->ctx.session_fi && m->h.seid_present) { struct up_session *session; session = pfcp_entity_peer_find_up_session_by_up_seid(entity_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, struct osmo_pfcp_msg *m) { struct pfcp_entity_peer *entity_peer = m->ctx.peer_fi ? m->ctx.peer_fi->priv : NULL; struct osmo_pfcp_ie_node_id *node_id = osmo_pfcp_msg_node_id(m); struct pfcp_node_peer *node_peer; OSMO_ASSERT(node_id); /* If PFCP entity belonged to a different PFCP Node (ie. Node Id changed), drop * old entity and re-create a new one on the new Node: */ if (entity_peer && osmo_pfcp_ie_node_id_cmp(&entity_peer->node_peer->node_id, node_id)) { OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "PFCP Entity Node-Id changed: %s -> %s!\n", osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, &entity_peer->node_peer->node_id), osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, node_id)); pfcp_entity_peer_remove_msg_ctx(entity_peer, m); pfcp_entity_peer_free(entity_peer); entity_peer = NULL; } if (!entity_peer) { OSMO_ASSERT(node_id); node_peer = up_endpoint_find_pfcp_node_peer(up_ep, node_id); if (!node_peer) { node_peer = pfcp_node_peer_alloc(up_ep, node_id); OSMO_ASSERT(node_peer); } entity_peer = pfcp_node_peer_find_entity_by_remote_addr(node_peer, &m->remote_addr); if (!entity_peer) { entity_peer = pfcp_entity_peer_alloc(node_peer, &m->remote_addr); OSMO_ASSERT(entity_peer); } pfcp_entity_peer_set_msg_ctx(entity_peer, m); } osmo_fsm_inst_dispatch(entity_peer->fi, PFCP_ENTITY_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, PFCP_ENTITY_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, PFCP_ENTITY_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); } /* Validate received F-SEID IP address */ static enum osmo_pfcp_cause up_ep_validate_cp_f_seid_addr(const struct up_endpoint *up_ep, const struct osmo_pfcp_ie_f_seid *f_seid) { const struct osmo_sockaddr *local_addr; OSMO_ASSERT(up_ep); OSMO_ASSERT(f_seid); /* Validate the F-SEID contains an IP address we can send requests to later on: */ local_addr = osmo_pfcp_endpoint_get_local_addr(up_ep->pfcp_ep); OSMO_ASSERT(local_addr); switch (local_addr->u.sa.sa_family) { case AF_INET: if (!f_seid->ip_addr.v4_present) return OSMO_PFCP_CAUSE_MANDATORY_IE_INCORRECT; break; case AF_INET6: if (!f_seid->ip_addr.v4_present && !f_seid->ip_addr.v6_present) return OSMO_PFCP_CAUSE_MANDATORY_IE_INCORRECT; break; default: OSMO_ASSERT(0); } return OSMO_PFCP_CAUSE_REQUEST_ACCEPTED; } static void up_ep_rx_session_est_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m) { struct osmo_pfcp_msg *resp; enum osmo_pfcp_cause cause; cause = up_ep_validate_cp_f_seid_addr(up_ep, &m->ies.session_est_req.cp_f_seid); if (cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) { OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Remote CP F-SEID IP address invalid\n"); goto nack_response; } if (!m->ctx.peer_fi) { OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot establish session\n"); cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC; goto nack_response; } osmo_fsm_inst_dispatch(m->ctx.peer_fi, PFCP_ENTITY_PEER_EV_RX_SESSION_EST_REQ, (void *)m); return; nack_response: resp = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_EST_RESP); resp->ies.session_est_resp.cause = cause; osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, resp); } static void up_ep_rx_session_mod_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m) { struct osmo_pfcp_msg *resp; enum osmo_pfcp_cause cause; if (m->ies.session_mod_req.cp_f_seid_present) { cause = up_ep_validate_cp_f_seid_addr(up_ep, &m->ies.session_mod_req.cp_f_seid); if (cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) { OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Remote CP F-SEID IP address invalid\n"); goto nack_response; } } if (!m->ctx.session_fi) { /* Session not found. */ 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"); cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC; goto nack_response; } else { OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "No established session with SEID=0x%"PRIx64", cannot modify\n", m->h.seid); cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND; goto nack_response; } } osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_MOD_REQ, (void *)m); return; nack_response: resp = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_MOD_RESP); resp->ies.session_mod_resp.cause = cause; osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, resp); } static void up_ep_rx_session_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m) { struct osmo_pfcp_msg *resp; enum osmo_pfcp_cause cause; #if 0 /* Can be enabled once libosmo-pfcp struct osmo_pfcp_msg_session_del_req supports the IE: */ if (m->ies.session_del_req.cp_f_seid_present) { cause = up_ep_validate_cp_f_seid_addr(up_ep, &m->ies.session_mod_req.cp_f_seid); if (cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED) { OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Remote CP F-SEID IP address invalid\n"); goto nack_response; } } #endif if (!m->ctx.session_fi) { /* Session not found. */ 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"); cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC; goto nack_response; } else { OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "No established session with SEID=0x%"PRIx64", cannot delete\n", m->h.seid); cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND; goto nack_response; } } osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_DEL_REQ, (void *)m); return; nack_response: resp = osmo_pfcp_msg_alloc_tx_resp(OTC_SELECT, m, OSMO_PFCP_MSGT_SESSION_DEL_RESP); resp->ies.session_del_resp.cause = cause; osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, resp); } 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->pfcp_node_peer_list); hash_init(up_ep->sessions_by_up_seid); 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); } struct pfcp_node_peer *up_endpoint_find_pfcp_node_peer(const struct up_endpoint *up_ep, const struct osmo_pfcp_ie_node_id *node_id) { struct pfcp_node_peer *node_peer; llist_for_each_entry(node_peer, &up_ep->pfcp_node_peer_list, entry) { if (osmo_pfcp_ie_node_id_cmp(&node_peer->node_id, node_id)) continue; return node_peer; } return NULL; } static struct up_session *up_endpoint_find_session(struct up_endpoint *ep, uint64_t up_seid) { struct up_session *session; hash_for_each_possible(ep->sessions_by_up_seid, session, ep_node_by_up_seid, up_seid) { if (session->up_seid == up_seid) 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 pfcp_node_peer *node_peer; struct up_endpoint *ep = *_ep; while ((node_peer = llist_first_entry_or_null(&ep->pfcp_node_peer_list, struct pfcp_node_peer, entry))) pfcp_node_peer_free(node_peer); osmo_pfcp_endpoint_free(&ep->pfcp_ep); *_ep = NULL; }