/* * (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 #include #include #include #include struct g_upf *g_upf = NULL; struct osmo_tdef g_upf_nft_tdefs[] = { { .T = -32, .default_val = 1000, .unit = OSMO_TDEF_MS, .desc = "How long to wait for more nft rulesets before flushing in batch", }, { .T = -33, .default_val = 1, .unit = OSMO_TDEF_CUSTOM, .desc = "When reaching this nr of queued nft rulesets, flush the queue", .max_val = 128, }, {} }; struct osmo_tdef_group g_upf_tdef_groups[] = { { "pfcp", "PFCP endpoint timers", osmo_pfcp_tdefs, }, { "nft", "netfilter timers", g_upf_nft_tdefs, }, {} }; void g_upf_alloc(void *ctx) { OSMO_ASSERT(g_upf == NULL); g_upf = talloc_zero(ctx, struct g_upf); *g_upf = (struct g_upf){ .pfcp = { .vty_cfg = { .local_addr = talloc_strdup(g_upf, UPF_PFCP_LISTEN_DEFAULT), .local_port = OSMO_PFCP_PORT, }, }, .tunmap = { .priority_pre = -300, .priority_post = 400, }, .tunend = { /* TODO: recovery count state file; use lower byte of current time, poor person's random. */ .recovery_count = time(NULL), }, }; INIT_LLIST_HEAD(&g_upf->tunend.vty_cfg.devs); INIT_LLIST_HEAD(&g_upf->tunend.devs); INIT_LLIST_HEAD(&g_upf->netinst); } int upf_pfcp_init(void) { struct osmo_sockaddr_str local_addr_str; struct osmo_sockaddr local_addr; OSMO_ASSERT(g_upf); OSMO_ASSERT(g_upf->pfcp.ep == NULL); /* Translate address string from VTY config to osmo_sockaddr: first read into osmo_sockaddr_str, then write to * osmo_sockaddr. */ osmo_sockaddr_str_from_str(&local_addr_str, g_upf->pfcp.vty_cfg.local_addr, g_upf->pfcp.vty_cfg.local_port); osmo_sockaddr_str_to_sockaddr(&local_addr_str, &local_addr.u.sas); g_upf->pfcp.ep = up_endpoint_alloc(g_upf, &local_addr); if (!g_upf->pfcp.ep) { fprintf(stderr, "Failed to allocate PFCP endpoint.\n"); return -1; } return 0; } int upf_pfcp_listen(void) { int rc; if (!g_upf->pfcp.ep) { rc = upf_pfcp_init(); if (rc) return rc; } rc = up_endpoint_bind(g_upf->pfcp.ep); if (rc) { LOGP(DLPFCP, LOGL_ERROR, "PFCP: failed to listen on %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_upf->pfcp.ep->pfcp_ep))); return rc; } LOGP(DLPFCP, LOGL_NOTICE, "PFCP: Listening on %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, osmo_pfcp_endpoint_get_local_addr(g_upf->pfcp.ep->pfcp_ep))); return 0; } int upf_gtp_devs_open() { struct tunend_vty_cfg *c = &g_upf->tunend.vty_cfg; struct tunend_vty_cfg_dev *d; llist_for_each_entry(d, &c->devs, entry) { if (upf_gtp_dev_open(d->dev_name, d->create, d->local_addr, false, false)) return -1; } return 0; } static bool upf_is_local_teid_in_use(uint32_t teid) { struct up_peer *peer; llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) { struct up_session *session = up_session_find_by_local_teid(peer, teid); if (session) return true; } return false; } static uint32_t upf_next_local_teid_inc(void) { g_upf->gtp.next_local_teid_state++; if (!g_upf->gtp.next_local_teid_state) g_upf->gtp.next_local_teid_state++; return g_upf->gtp.next_local_teid_state; } uint32_t upf_next_local_teid(void) { uint32_t sanity; for (sanity = 2342; sanity; sanity--) { uint32_t next_teid = upf_next_local_teid_inc(); if (upf_is_local_teid_in_use(next_teid)) continue; return next_teid; } return 0; } static uint32_t upf_next_chain_id_inc(void) { g_upf->tunmap.next_chain_id_state++; if (!g_upf->tunmap.next_chain_id_state) g_upf->tunmap.next_chain_id_state++; return g_upf->tunmap.next_chain_id_state; } /* Return an unused chain_id, or 0 if none is found with sane effort. */ uint32_t upf_next_chain_id(void) { uint32_t sanity; /* Make sure the new chain_id is not used anywhere */ for (sanity = 2342; sanity; sanity--) { struct up_peer *peer; uint32_t chain_id = upf_next_chain_id_inc(); bool taken = false; if (!g_upf->pfcp.ep) return chain_id; llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) { struct up_session *session; int bkt; hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) { struct up_gtp_action *a; llist_for_each_entry(a, &session->active_gtp_actions, entry) { if (a->kind != UP_GTP_U_TUNMAP) continue; if (a->tunmap.access.chain_id == chain_id || a->tunmap.core.chain_id == chain_id) { taken = true; break; } } if (taken) break; } if (taken) break; } if (!taken) return chain_id; } /* finding a chain_id became insane, return invalid = 0 */ return 0; }