neels has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-upf/+/28309 )
Change subject: add nft ......................................................................
add nft
Related: SYS#5599 Change-Id: Ic0d319eb4f98cd51a5999c804c4203ab0bdda650 --- M configure.ac M include/osmocom/upf/upf.h M include/osmocom/upf/upf_nft.h M src/osmo-upf/Makefile.am M src/osmo-upf/osmo_upf_main.c M src/osmo-upf/up_gtp_action.c M src/osmo-upf/up_session.c M src/osmo-upf/upf.c A src/osmo-upf/upf_nft.c M src/osmo-upf/upf_vty.c 10 files changed, 419 insertions(+), 6 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-upf refs/changes/09/28309/1
diff --git a/configure.ac b/configure.ac index 119aa6c..fea27c6 100644 --- a/configure.ac +++ b/configure.ac @@ -45,6 +45,8 @@ PKG_CHECK_MODULES(LIBOSMOGTLV, libosmo-gtlv >= 0.1.0) PKG_CHECK_MODULES(LIBOSMOPFCP, libosmo-pfcp >= 0.1.0) PKG_CHECK_MODULES(LIBGTPNL, libgtpnl >= 1.2.0) +PKG_CHECK_MODULES(LIBNFTNL, libnftnl >= 1.2.1) +PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2)
dnl checks for header files AC_HEADER_STDC diff --git a/include/osmocom/upf/upf.h b/include/osmocom/upf/upf.h index b8065dd..49217c6 100644 --- a/include/osmocom/upf/upf.h +++ b/include/osmocom/upf/upf.h @@ -33,6 +33,7 @@ struct ctrl_handle;
struct upf_gtp_dev; +struct nft_ctx;
#define UPF_PFCP_LISTEN_DEFAULT "0.0.0.0"
@@ -72,6 +73,8 @@ struct pfcp_vty_cfg vty_cfg; struct up_endpoint *ep; } pfcp; + + /* Tunnel encaps decaps via GTP kernel module */ struct { /* GTP devices as in osmo-upf.cfg */ struct gtp_vty_cfg vty_cfg; @@ -82,6 +85,14 @@ struct mnl_socket *nl; int32_t genl_id; } gtp; + + /* Tunnel forwarding via linux netfilter */ + struct { + struct nft_ctx *nft_ctx; + char *table_name; + int priority; + uint32_t next_id_state; + } nft; };
extern struct g_upf *g_upf; @@ -91,6 +102,7 @@ DPEER, DSESSION, DGTP, + DNFT, };
void g_upf_alloc(void *ctx); diff --git a/include/osmocom/upf/upf_nft.h b/include/osmocom/upf/upf_nft.h index 569eadb..113bfd8 100644 --- a/include/osmocom/upf/upf_nft.h +++ b/include/osmocom/upf/upf_nft.h @@ -27,6 +27,8 @@
#include <osmocom/core/socket.h>
+#define NFT_CHAIN_NAME_PREFIX_TUNMAP "tunmap" + struct upf_nft_tunmap_desc { struct { struct osmo_sockaddr gtp_remote_addr; diff --git a/src/osmo-upf/Makefile.am b/src/osmo-upf/Makefile.am index d8aa5a0..1265051 100644 --- a/src/osmo-upf/Makefile.am +++ b/src/osmo-upf/Makefile.am @@ -13,11 +13,15 @@ $(LIBOSMOGTLV_CFLAGS) \ $(LIBOSMOPFCP_CFLAGS) \ $(LIBGTPNL_CFLAGS) \ + $(LIBNFTNL_CFLAGS) \ + $(LIBNFTABLES_CFLAGS) \ $(COVERAGE_CFLAGS) \ $(NULL)
AM_LDFLAGS = \ $(LIBGTPNL_LDFLAGS) \ + $(LIBNFTNL_LDFLAGS) \ + $(LIBNFTABLES_LDFLAGS) \ $(COVERAGE_LDFLAGS) \ $(NULL)
@@ -33,6 +37,7 @@ up_session.c \ upf.c \ upf_gtp.c \ + upf_nft.c \ upf_vty.c \ $(NULL)
@@ -43,5 +48,7 @@ $(LIBOSMOGTLV_LIBS) \ $(LIBOSMOPFCP_LIBS) \ $(LIBGTPNL_LIBS) \ + $(LIBNFTNL_LIBS) \ + $(LIBNFTABLES_LIBS) \ $(COVERAGE_LDFLAGS) \ $(NULL) diff --git a/src/osmo-upf/osmo_upf_main.c b/src/osmo-upf/osmo_upf_main.c index 302d957..81102dd 100644 --- a/src/osmo-upf/osmo_upf_main.c +++ b/src/osmo-upf/osmo_upf_main.c @@ -44,6 +44,7 @@ #include <osmocom/upf/upf.h> #include <osmocom/upf/up_endpoint.h> #include <osmocom/upf/upf_gtp.h> +#include <osmocom/upf/upf_nft.h>
#define _GNU_SOURCE #include <getopt.h> @@ -237,6 +238,12 @@ .enabled = 0, .loglevel = LOGL_NOTICE, .color = OSMO_LOGCOLOR_PURPLE, }, + [DNFT] = { + .name = "DNFT", + .description = "GTP forwarding rules via linux netfilter", + .enabled = 0, .loglevel = LOGL_NOTICE, + .color = OSMO_LOGCOLOR_PURPLE, + }, };
const struct log_info log_info = { @@ -330,6 +337,9 @@ if (upf_gtp_devs_open()) return -1;
+ if (upf_nft_init()) + return -1; + if (upf_pfcp_listen()) return -1;
@@ -356,6 +366,8 @@
upf_gtp_genl_close();
+ upf_nft_free(); + /* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */ talloc_report_full(tall_upf_ctx, stderr); talloc_free(tall_upf_ctx); diff --git a/src/osmo-upf/up_gtp_action.c b/src/osmo-upf/up_gtp_action.c index 840944e..cf5981e 100644 --- a/src/osmo-upf/up_gtp_action.c +++ b/src/osmo-upf/up_gtp_action.c @@ -100,9 +100,36 @@ } LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "%s GTP tunnel\n", enable ? "Enabled" : "Disabled"); return 0; + case UP_GTP_U_TUNMAP: - LOG_UP_GTP_ACTION(a, LOGL_ERROR, "TEID translation not yet implemented\n"); - return -ENOTSUP; + if (enable && a->tunmap.id != 0) { + LOG_UP_GTP_ACTION(a, LOGL_ERROR, + "Cannot enable: nft GTP tunnel mapping rule has been enabled before" + " as " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u\n", a->tunmap.id); + return -EALREADY; + } + if (!enable && a->tunmap.id == 0) { + LOG_UP_GTP_ACTION(a, LOGL_ERROR, + "Cannot disable: nft GTP tunnel mapping rule has not been enabled" + " (no " NFT_CHAIN_NAME_PREFIX_TUNMAP " id)\n"); + return -ENOENT; + } + if (enable) + rc = upf_nft_tunmap_create(&a->tunmap); + else + rc = upf_nft_tunmap_delete(&a->tunmap); + if (rc) { + LOG_UP_GTP_ACTION(a, LOGL_ERROR, + "Failed to %s nft GTP tunnel mapping " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u:" + " %d %s\n", enable ? "enable" : "disable", a->tunmap.id, rc, strerror(-rc)); + return rc; + } + LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "%s nft GTP tunnel mapping " NFT_CHAIN_NAME_PREFIX_TUNMAP "%u\n", + enable ? "Enabled" : "Disabled", a->tunmap.id); + if (!enable) + a->tunmap.id = 0; + return 0; + default: LOG_UP_GTP_ACTION(a, LOGL_ERROR, "Invalid action\n"); return -ENOTSUP; diff --git a/src/osmo-upf/up_session.c b/src/osmo-upf/up_session.c index d1479b1..00299d3 100644 --- a/src/osmo-upf/up_session.c +++ b/src/osmo-upf/up_session.c @@ -1141,7 +1141,107 @@
static void add_gtp_action_forw(void *ctx, struct llist_head *dst, struct pdr *pdr) { - /* TODO implement GTP forwarding with TEID translation */ + struct up_session *session = pdr->session; + struct up_gtp_action *a; + struct pdr *rpdr; + struct far *far; + struct osmo_pfcp_ie_forw_params *far_forw; + struct far *rfar; + struct osmo_pfcp_ie_forw_params *rfar_forw; + + OSMO_ASSERT(pdr->far); + OSMO_ASSERT(pdr->reverse_pdr); + OSMO_ASSERT(pdr->reverse_pdr->far); + + far = pdr->far; + far_forw = &far->desc.forw_params; + rpdr = pdr->reverse_pdr; + rfar = rpdr->far; + rfar_forw = &rfar->desc.forw_params; + + /* To decaps, we need to have a local TEID assigned for which to receive GTP packets. */ + /* decaps from CORE */ + if (!pdr->local_f_teid || pdr->local_f_teid->choose_flag) { + log_inactive_pdr_set(pdr, "missing local TEID (CORE side)", pdr); + return; + } + /* decaps from ACCESS */ + if (!rpdr->local_f_teid || rpdr->local_f_teid->choose_flag) { + log_inactive_pdr_set(pdr, "missing local TEID (ACCESS side)", pdr); + return; + } + + /* To encaps, we need to have a remote TEID assigned to send out in GTP packets, and we need to know where to + * send GTP to. */ + /* encaps towards ACCESS */ + if (!far->desc.forw_params_present) { + log_inactive_pdr_set(pdr, "missing FAR Forwarding Parameters", pdr); + return; + } + if (!far_forw->outer_header_creation_present) { + log_inactive_pdr_set(pdr, "missing FAR Outer Header Creation", pdr); + return; + } + if (!far_forw->outer_header_creation.teid_present) { + log_inactive_pdr_set(pdr, "missing TEID in FAR Outer Header Creation", pdr); + return; + } + if (!far_forw->outer_header_creation.ip_addr.v4_present) { + log_inactive_pdr_set(pdr, "missing IPv4 in FAR Outer Header Creation", pdr); + return; + } + /* encaps towards CORE */ + if (!rfar->desc.forw_params_present) { + log_inactive_pdr_set(pdr, "missing FAR Forwarding Parameters", rpdr); + return; + } + if (!rfar_forw->outer_header_creation_present) { + log_inactive_pdr_set(pdr, "missing FAR Outer Header Creation", rpdr); + return; + } + if (!rfar_forw->outer_header_creation.teid_present) { + log_inactive_pdr_set(pdr, "missing TEID in FAR Outer Header Creation", rpdr); + return; + } + if (!rfar_forw->outer_header_creation.ip_addr.v4_present) { + log_inactive_pdr_set(pdr, "missing IPv4 in FAR Outer Header Creation", rpdr); + return; + } + + pdr->active = true; + far->active = true; + rpdr->active = true; + rfar->active = true; + LOGPFSML(session->fi, LOGL_DEBUG, "Active PDR set: %s\n", pdr_to_str_c(OTC_SELECT, pdr)); + LOGPFSML(session->fi, LOGL_DEBUG, "Active PDR set: + %s\n", pdr_to_str_c(OTC_SELECT, rpdr)); + + talloc_free(pdr->inactive_reason); + pdr->inactive_reason = NULL; + talloc_free(rpdr->inactive_reason); + rpdr->inactive_reason = NULL; + + a = talloc(ctx, struct up_gtp_action); + OSMO_ASSERT(a); + *a = (struct up_gtp_action){ + .session = session, + .pdr_core = pdr->desc.pdr_id, + .pdr_access = rpdr->desc.pdr_id, + .kind = UP_GTP_U_TUNMAP, + .tunmap = { + .core = { + .local_teid = pdr->local_f_teid->fixed.teid, + .remote_teid = rfar_forw->outer_header_creation.teid, + .gtp_remote_addr = rfar_forw->outer_header_creation.ip_addr.v4, + }, + .access = { + .local_teid = rpdr->local_f_teid->fixed.teid, + .remote_teid = far_forw->outer_header_creation.teid, + .gtp_remote_addr = far_forw->outer_header_creation.ip_addr.v4, + }, + }, + }; + + llist_add_tail(&a->entry, dst); }
/* Analyse all PDRs and FARs and find configurations that match either a GTP encaps/decaps or a GTP forward rule. Add to @@ -1294,11 +1394,12 @@ { struct llist_head empty; INIT_LLIST_HEAD(&empty); + /* Call setup_gtp_actions() with an empty list, to clean up all active GTP actions */ setup_gtp_actions(session, &empty); }
-/* Check whether the Packet Detection and Forwarding Action Rules amount to an encaps/decaps of GTP or a GTP forwarding, - * or none of the two. */ +/* Check whether the Packet Detection and Forwarding Action Rules amount to an encaps/decaps of GTP or a GTP tunnel + * mapping, or none of the two. */ static enum osmo_pfcp_cause up_session_setup_gtp(struct up_session *session) { enum osmo_pfcp_cause cause; diff --git a/src/osmo-upf/upf.c b/src/osmo-upf/upf.c index 234368a..9677147 100644 --- a/src/osmo-upf/upf.c +++ b/src/osmo-upf/upf.c @@ -50,6 +50,9 @@ .local_port = OSMO_PFCP_PORT, }, }, + .nft = { + .priority = -300, + }, };
INIT_LLIST_HEAD(&g_upf->gtp.vty_cfg.devs); diff --git a/src/osmo-upf/upf_nft.c b/src/osmo-upf/upf_nft.c new file mode 100644 index 0000000..d93dc39 --- /dev/null +++ b/src/osmo-upf/upf_nft.c @@ -0,0 +1,207 @@ +/* + * (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.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; + } + return 0; +} + +int upf_nft_init() +{ + int rc; + 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; + } + + if (!g_upf->nft.table_name) + g_upf->nft.table_name = talloc_strdup(g_upf, "osmo-upf"); + + 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; + /* The TEID that we send to the peer in GTP packets. */ + uint32_t teid_remote; + /* 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 from from_peer */ + OSMO_STRBUF_PRINTF(sb, " ip saddr "); + OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, from_peer->addr); + + /* Match on the TEID in the header */ + OSMO_STRBUF_PRINTF(sb, " @ih,32,32 0x%08x", from_peer->teid_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); + + /* 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\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) +{ + *args = (struct upf_nft_args){ + .table_name = g_upf->nft.table_name, + .chain_id = tunmap->id, + .priority = g_upf->nft.priority, + .peer_a = { + .addr = &tunmap->access.gtp_remote_addr, + .teid_remote = tunmap->access.remote_teid, + .teid_local = tunmap->access.local_teid, + }, + .peer_b = { + .addr = &tunmap->core.gtp_remote_addr, + .teid_remote = tunmap->core.remote_teid, + .teid_local = tunmap->core.local_teid, + }, + }; +} + +int upf_nft_tunmap_create(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 */ + 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_run(upf_nft_ruleset_tunmap_create_c(OTC_SELECT, &args)); +} + +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)); +} diff --git a/src/osmo-upf/upf_vty.c b/src/osmo-upf/upf_vty.c index 73d86d1..0e51cd4 100644 --- a/src/osmo-upf/upf_vty.c +++ b/src/osmo-upf/upf_vty.c @@ -41,6 +41,7 @@ enum upf_vty_node { PFCP_NODE = _LAST_OSMOVTY_NODE + 1, GTP_NODE, + NFT_NODE, };
static struct cmd_node cfg_pfcp_node = { @@ -85,7 +86,7 @@
DEFUN(cfg_gtp, cfg_gtp_cmd, "gtp", - "Enter the GTP configuration node\n") + "Enter the 'gtp' node to configure GTP kernel module usage\n") { vty->node = GTP_NODE; return CMD_SUCCESS; @@ -169,6 +170,40 @@ return CMD_SUCCESS; }
+static struct cmd_node cfg_nft_node = { + NFT_NODE, + "%s(config-nft)# ", + 1, +}; + +DEFUN(cfg_nft, cfg_nft_cmd, + "nft", + "Enter the 'nft' node to configure nftables usage\n") +{ + vty->node = NFT_NODE; + return CMD_SUCCESS; +} + +static int config_write_nft(struct vty *vty) +{ + vty_out(vty, "nft%s", VTY_NEWLINE); + + if (g_upf->nft.table_name && strcmp(g_upf->nft.table_name, "osmo-upf")) + vty_out(vty, " table-name %s%s", g_upf->nft.table_name, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_nft_table_name, cfg_nft_table_name_cmd, + "table-name TABLE_NAME", + "Set the nft inet table name to create and place GTP tunnel forwarding chains in" + " (as in 'nft add table inet foo'). If multiple instances of osmo-upf are running on the same system, each" + " osmo-upf must have its own table name. Otherwise the names of created forwarding chains will collide.\n" + "nft inet table name\n") +{ + osmo_talloc_replace_string(g_upf, &g_upf->nft.table_name, argv[0]); + return CMD_SUCCESS; +} + DEFUN(show_pdr, show_pdr_cmd, "show pdr", SHOW_STR @@ -282,5 +317,10 @@ install_element(GTP_NODE, &cfg_gtp_dev_create_cmd); install_element(GTP_NODE, &cfg_gtp_dev_use_cmd); install_element(GTP_NODE, &cfg_gtp_dev_del_cmd); + + install_node(&cfg_nft_node, config_write_nft); + install_element(CONFIG_NODE, &cfg_nft_cmd); + + install_element(NFT_NODE, &cfg_nft_table_name_cmd); }