251 lines
7.8 KiB
C
251 lines
7.8 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 <errno.h>
|
|
#include <nftables/libnftables.h>
|
|
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/core/logging.h>
|
|
|
|
#include <osmocom/upf/upf.h>
|
|
#include <osmocom/upf/upf_nft.h>
|
|
|
|
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));
|
|
}
|