/* * (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 char *upf_nft_ruleset_table_create(void *ctx, const char *table_name) { return talloc_asprintf(ctx, "add table inet %s\n", table_name); } static int upf_nft_run(const char *ruleset) { int rc; if (g_upf->nft.mockup) { LOGP(DNFT, LOGL_NOTICE, "tunmap/mockup active: not running nft ruleset: '%s'\n", ruleset); return 0; } if (!g_upf->nft.nft_ctx) { rc = upf_nft_init(); if (rc) return rc; } rc = nft_run_cmd_from_buffer(g_upf->nft.nft_ctx, ruleset); if (rc < 0) { LOGP(DNFT, LOGL_ERROR, "error running nft ruleset: rc=%d ruleset=%s\n", rc, osmo_quote_str_c(OTC_SELECT, ruleset, -1)); return -EIO; } LOGP(DNFT, LOGL_DEBUG, "set up nft ruleset: %s\n", osmo_quote_str_c(OTC_SELECT, ruleset, -1)); return 0; } int upf_nft_init() { int rc; /* Always set up the default settings, also in mockup mode, so that the VTY reflects sane values */ if (!g_upf->nft.table_name) g_upf->nft.table_name = talloc_strdup(g_upf, "osmo-upf"); /* When in mockup mode, do not set up nft_ctx and netfilter table */ if (g_upf->nft.mockup) { LOGP(DNFT, LOGL_NOTICE, "tunmap/mockup active: not allocating libnftables nft_ctx. FOR TESTING PURPOSES ONLY.\n"); return 0; } g_upf->nft.nft_ctx = nft_ctx_new(NFT_CTX_DEFAULT); if (!g_upf->nft.nft_ctx) { LOGP(DNFT, LOGL_ERROR, "cannot allocate libnftables nft_ctx\n"); return -EIO; } rc = upf_nft_run(upf_nft_ruleset_table_create(OTC_SELECT, g_upf->nft.table_name)); if (rc) { LOGP(DNFT, LOGL_ERROR, "Failed to create nft table %s\n", osmo_quote_str_c(OTC_SELECT, g_upf->nft.table_name, -1)); return rc; } LOGP(DNFT, LOGL_NOTICE, "Created nft table %s\n", osmo_quote_str_c(OTC_SELECT, g_upf->nft.table_name, -1)); return 0; } int upf_nft_free() { if (!g_upf->nft.nft_ctx) return 0; nft_ctx_free(g_upf->nft.nft_ctx); g_upf->nft.nft_ctx = NULL; return 0; } struct upf_nft_args_peer { /* The source IP address in packets received from this peer */ const struct osmo_sockaddr *addr_remote; /* The TEID that we send to the peer in GTP packets. */ uint32_t teid_remote; /* The local destination IP address in packets received from this peer */ const struct osmo_sockaddr *addr_local; /* The TEID that the peer sends to us in GTP packets. */ uint32_t teid_local; }; struct upf_nft_args { /* global table name */ const char *table_name; /* chain name for this specific tunnel mapping */ uint32_t chain_id; int priority; struct upf_nft_args_peer peer_a; struct upf_nft_args_peer peer_b; }; static int tunmap_single_direction(char *buf, size_t buflen, const struct upf_nft_args *args, const struct upf_nft_args_peer *from_peer, const struct upf_nft_args_peer *to_peer) { struct osmo_strbuf sb = { .buf = buf, .len = buflen }; OSMO_STRBUF_PRINTF(sb, "add rule inet %s " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u", args->table_name, args->chain_id); /* Match only UDP packets */ OSMO_STRBUF_PRINTF(sb, " meta l4proto udp"); /* Match on packets coming in at specific local IP */ OSMO_STRBUF_PRINTF(sb, " ip daddr "); OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, from_peer->addr_local); /* Match on the TEID in the header */ OSMO_STRBUF_PRINTF(sb, " @ih,32,32 0x%08x", from_peer->teid_local); /* Change outgoing address to local IP on outgoing interface */ OSMO_STRBUF_PRINTF(sb, " ip saddr set "); OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, to_peer->addr_local); /* Change destination address to to_peer */ OSMO_STRBUF_PRINTF(sb, " ip daddr set "); OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, to_peer->addr_remote); /* Change the TEID in the header to the one to_peer expects */ OSMO_STRBUF_PRINTF(sb, " @ih,32,32 set 0x%08x", to_peer->teid_remote); OSMO_STRBUF_PRINTF(sb, " counter"); OSMO_STRBUF_PRINTF(sb, " accept"); OSMO_STRBUF_PRINTF(sb, ";\n"); return sb.chars_needed; } static int upf_nft_ruleset_tunmap_create_buf(char *buf, size_t buflen, const struct upf_nft_args *args) { struct osmo_strbuf sb = { .buf = buf, .len = buflen }; /* Add a chain for this tunnel mapping */ OSMO_STRBUF_PRINTF(sb, "add chain inet %s " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u { type filter hook prerouting priority %d; }\n", args->table_name, args->chain_id, args->priority); /* Forwarding from peer_a to peer_b */ OSMO_STRBUF_APPEND(sb, tunmap_single_direction, args, &args->peer_a, &args->peer_b); /* And from peer_b to peer_a */ OSMO_STRBUF_APPEND(sb, tunmap_single_direction, args, &args->peer_b, &args->peer_a); return sb.chars_needed; } static char *upf_nft_ruleset_tunmap_create_c(void *ctx, const struct upf_nft_args *args) { OSMO_NAME_C_IMPL(ctx, 512, "ERROR", upf_nft_ruleset_tunmap_create_buf, args) } static int upf_nft_ruleset_tunmap_delete_buf(char *buf, size_t buflen, const struct upf_nft_args *args) { struct osmo_strbuf sb = { .buf = buf, .len = buflen }; OSMO_STRBUF_PRINTF(sb, "delete chain inet %s " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u\n", args->table_name, args->chain_id); return sb.chars_needed; } static char *upf_nft_ruleset_tunmap_delete_c(void *ctx, const struct upf_nft_args *args) { OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_nft_ruleset_tunmap_delete_buf, args) } static void upf_nft_args_from_tunmap_desc(struct upf_nft_args *args, const struct upf_nft_tunmap_desc *tunmap) { OSMO_ASSERT(osmo_sockaddr_port(&tunmap->access.gtp_remote_addr.u.sa) == 0); OSMO_ASSERT(osmo_sockaddr_port(&tunmap->access.gtp_local_addr.u.sa) == 0); OSMO_ASSERT(osmo_sockaddr_port(&tunmap->core.gtp_remote_addr.u.sa) == 0); OSMO_ASSERT(osmo_sockaddr_port(&tunmap->core.gtp_local_addr.u.sa) == 0); *args = (struct upf_nft_args){ .table_name = g_upf->nft.table_name, .chain_id = tunmap->id, .priority = g_upf->nft.priority, .peer_a = { .addr_remote = &tunmap->access.gtp_remote_addr, .teid_remote = tunmap->access.remote_teid, .addr_local = &tunmap->access.gtp_local_addr, .teid_local = tunmap->access.local_teid, }, .peer_b = { .addr_remote = &tunmap->core.gtp_remote_addr, .teid_remote = tunmap->core.remote_teid, .addr_local = &tunmap->core.gtp_local_addr, .teid_local = tunmap->core.local_teid, }, }; } char *upf_nft_tunmap_get_ruleset_str(void *ctx, struct upf_nft_tunmap_desc *tunmap) { struct upf_nft_args args; /* Give this tunnel mapping a new id, returned to the caller so that the tunnel mapping can be deleted later */ if (!tunmap->id) { g_upf->nft.next_id_state++; if (!g_upf->nft.next_id_state) g_upf->nft.next_id_state++; tunmap->id = g_upf->nft.next_id_state; } upf_nft_args_from_tunmap_desc(&args, tunmap); return upf_nft_ruleset_tunmap_create_c(ctx, &args); } int upf_nft_tunmap_create(struct upf_nft_tunmap_desc *tunmap) { return upf_nft_run(upf_nft_tunmap_get_ruleset_str(OTC_SELECT, tunmap)); } int upf_nft_tunmap_delete(struct upf_nft_tunmap_desc *tunmap) { struct upf_nft_args args; upf_nft_args_from_tunmap_desc(&args, tunmap); return upf_nft_run(upf_nft_ruleset_tunmap_delete_c(OTC_SELECT, &args)); }