From nobody Mon Sep 16 20:11:52 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) client-ip=170.10.129.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1682911324; cv=none; d=zohomail.com; s=zohoarc; b=fidrasL3WQBX6jBmAKSk6lKxoES67ERdnIiW2Ov8UOmijUlnjF1TkMXQ51aKa68THAHcQtphdIqMMueLaIgVql4cox36om80opVrSZd8wx7Y8p44GMMWNL7FwyNL5u9a6tpDUtAw1yGukfKF+lbTtScvIblytbeczyVokxr1Nkg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1682911324; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=UKzEXZf2bxiQetg2LjSQBpi0PtOS5lN+tmCNohRLzOQ=; b=VKY3uI5+a8QFWrMiwgV0SsvhfNg3f3DYBqjUjqmUw6VPtk2KhJ4VyvA+hqE6SQRtElAgQDJ7EVvB2uCz3r7p4ltdi7TiF3B1ZKA5W2SvXAx9NhwCvAK0COdgkHqRqLgDwnTaoHpUoHoOBFvPLIm+0FXiWqi3qPsFQJ2D4qCJoio= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by mx.zohomail.com with SMTPS id 1682911324316639.6094655346253; Sun, 30 Apr 2023 20:22:04 -0700 (PDT) Received: from mimecast-mx02.redhat.com (mx3-rdu2.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-146-fzAW399rMSqXbKaPeDjzJg-1; Sun, 30 Apr 2023 23:20:32 -0400 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.rdu2.redhat.com [10.11.54.2]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id CD0693C1410E; Mon, 1 May 2023 03:20:24 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com [10.30.29.100]) by smtp.corp.redhat.com (Postfix) with ESMTP id AE2D040C945A; Mon, 1 May 2023 03:20:24 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (localhost [IPv6:::1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id E9A3F193F511; Mon, 1 May 2023 03:20:14 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx10.intmail.prod.int.rdu2.redhat.com [10.11.54.10]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id D12AC1946A44 for ; Mon, 1 May 2023 03:20:05 +0000 (UTC) Received: by smtp.corp.redhat.com (Postfix) id 8E47F475021; Mon, 1 May 2023 03:19:46 +0000 (UTC) Received: from vhost3.router.laine.org (unknown [10.22.8.105]) by smtp.corp.redhat.com (Postfix) with ESMTP id 72674463ECA for ; Mon, 1 May 2023 03:19:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1682911323; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=UKzEXZf2bxiQetg2LjSQBpi0PtOS5lN+tmCNohRLzOQ=; b=Plm8XxMxzxSzj+x+Z+hWBV/lA/b/RPE15j7uUla6ENuIgcdhd7ap5YcC/pBS5OuTvkTdYn W5lHahuDU05Bb6ggDkr1gl7JeZhn74BSr2Y8Ro469eRIb+DQvyaQugqj4AwvwAL3rOR2yK hm+aiTia7bccbIF2ppfNB4dK3vWQn8w= X-MC-Unique: fzAW399rMSqXbKaPeDjzJg-1 X-Original-To: libvir-list@listman.corp.redhat.com From: Laine Stump To: libvir-list@redhat.com Subject: [libvirt PATCH 16/28] util: add nftables backend to virnetfilter API used by network driver Date: Sun, 30 Apr 2023 23:19:31 -0400 Message-Id: <20230501031943.288145-17-laine@redhat.com> In-Reply-To: <20230501031943.288145-1-laine@redhat.com> References: <20230501031943.288145-1-laine@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.10 X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libvir-list-bounces@redhat.com Sender: "libvir-list" X-Scanned-By: MIMEDefang 3.1 on 10.11.54.2 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1682911326237100001 Content-Type: text/plain; charset="utf-8"; x-default="true" Signed-off-by: Laine Stump --- po/POTFILES | 1 + src/network/bridge_driver_conf.c | 4 + src/network/network.conf | 17 +- src/util/meson.build | 1 + src/util/virfirewall.c | 3 +- src/util/virfirewall.h | 1 + src/util/virnetfilter.c | 48 +++ src/util/virnftables.c | 594 +++++++++++++++++++++++++++++++ src/util/virnftables.h | 118 ++++++ 9 files changed, 784 insertions(+), 3 deletions(-) create mode 100644 src/util/virnftables.c create mode 100644 src/util/virnftables.h diff --git a/po/POTFILES b/po/POTFILES index d20ac36062..4966f71eb3 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -304,6 +304,7 @@ src/util/virnetdevveth.c src/util/virnetdevvportprofile.c src/util/virnetfilter.c src/util/virnetlink.c +src/util/virnftables.c src/util/virnodesuspend.c src/util/virnuma.c src/util/virnvme.c diff --git a/src/network/bridge_driver_conf.c b/src/network/bridge_driver_c= onf.c index 9769ee06b5..d9f07cf448 100644 --- a/src/network/bridge_driver_conf.c +++ b/src/network/bridge_driver_conf.c @@ -98,6 +98,7 @@ virNetworkLoadDriverConfig(virNetworkDriverConfig *cfg G_= GNUC_UNUSED, * for binaries used by the backends, and set accordingly. */ g_autofree char *iptablesInPath =3D NULL; + g_autofree char *nftInPath =3D NULL; =20 /* virFindFileInPath() uses g_find_program_in_path(), * which allows absolute paths, and verifies that @@ -105,6 +106,9 @@ virNetworkLoadDriverConfig(virNetworkDriverConfig *cfg = G_GNUC_UNUSED, */ if ((iptablesInPath =3D virFindFileInPath(IPTABLES))) cfg->firewallBackend =3D VIR_FIREWALL_BACKEND_IPTABLES; + else if ((nftInPath =3D virFindFileInPath(NFT))) + cfg->firewallBackend =3D VIR_FIREWALL_BACKEND_NFTABLES; + =20 if (cfg->firewallBackend =3D=3D VIR_FIREWALL_BACKEND_UNSET) VIR_INFO("firewall_backend not set, and no usable backend auto= -detected"); diff --git a/src/network/network.conf b/src/network/network.conf index 74c79e4cc6..630c4387a1 100644 --- a/src/network/network.conf +++ b/src/network/network.conf @@ -5,7 +5,20 @@ # firewall_backend: # # determines which subsystem to use to setup firewall packet -# filtering rules for virtual networks. Currently the only supported -# selection is "iptables". +# filtering rules for virtual networks. +# +# Supported settings: +# +# iptables - use iptables commands to construct the firewall +# nftables - use nft commands to construct the firewall +# +# For backward compatibility, and to reduce surprises, the +# default setting is "iptables". +# +# (NB: switching from one backend to another while there are active +# virtual networks *is* supported. The change will take place the +# next time that libvirtd/virtnetworkd is restarted - all existing +# virtual networks will have their old firewalls removed, and then +# reloaded using the new backend.) # #firewall_backend =3D "iptables" diff --git a/src/util/meson.build b/src/util/meson.build index aa570ed02a..c0e71760b1 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -71,6 +71,7 @@ util_sources =3D [ 'virnetdevvportprofile.c', 'virnetfilter.c', 'virnetlink.c', + 'virnftables.c', 'virnodesuspend.c', 'virnuma.c', 'virnvme.c', diff --git a/src/util/virfirewall.c b/src/util/virfirewall.c index fa21266fb2..17acc2adc3 100644 --- a/src/util/virfirewall.c +++ b/src/util/virfirewall.c @@ -39,7 +39,8 @@ VIR_LOG_INIT("util.firewall"); VIR_ENUM_IMPL(virFirewallBackend, VIR_FIREWALL_BACKEND_LAST, "UNSET", /* not yet set */ - "iptables"); + "iptables", + "nftables"); =20 typedef struct _virFirewallGroup virFirewallGroup; =20 diff --git a/src/util/virfirewall.h b/src/util/virfirewall.h index 020dd2bedb..4d03dc3b3b 100644 --- a/src/util/virfirewall.h +++ b/src/util/virfirewall.h @@ -46,6 +46,7 @@ typedef enum { typedef enum { VIR_FIREWALL_BACKEND_UNSET, VIR_FIREWALL_BACKEND_IPTABLES, + VIR_FIREWALL_BACKEND_NFTABLES, =20 VIR_FIREWALL_BACKEND_LAST, } virFirewallBackend; diff --git a/src/util/virnetfilter.c b/src/util/virnetfilter.c index e6a748e877..0fc541687e 100644 --- a/src/util/virnetfilter.c +++ b/src/util/virnetfilter.c @@ -29,6 +29,7 @@ #include "internal.h" #include "virnetfilter.h" #include "viriptables.h" +#include "virnftables.h" #include "vircommand.h" #include "viralloc.h" #include "virerror.h" @@ -75,6 +76,9 @@ virNetfilterApplyFirewallRule(virFirewall *fw, case VIR_FIREWALL_BACKEND_IPTABLES: return virIptablesApplyFirewallRule(fw, rule, output); =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + return virNftablesApplyFirewallRule(fw, rule, output); + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: virNetFilterBackendUnsetError(); @@ -101,6 +105,9 @@ virNetfilterSetupPrivateChains(virFirewallBackend backe= nd, case VIR_FIREWALL_BACKEND_IPTABLES: return iptablesSetupPrivateChains(layer); =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + return virNftablesSetupPrivateChains(layer); + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: virNetFilterBackendUnsetError(); @@ -123,6 +130,10 @@ virNetfilterInput(virFirewall *fw, iptablesInput(fw, layer, iface, port, action, tcp); break; =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + virNftablesInput(fw, layer, iface, port, action, tcp); + break; + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: break; @@ -143,6 +154,10 @@ virNetfilterOutput(virFirewall *fw, iptablesOutput(fw, layer, iface, port, action, tcp); break; =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + virNftablesOutput(fw, layer, iface, port, action, tcp); + break; + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: break; @@ -163,6 +178,10 @@ virNetfilterForwardAllowOut(virFirewall *fw, return iptablesForwardAllowOut(fw, netaddr, prefix, iface, physdev, action); =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + return virNftablesForwardAllowOut(fw, netaddr, prefix, + iface, physdev, action); + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: virNetFilterBackendUnsetError(); @@ -185,6 +204,10 @@ virNetfilterForwardAllowRelatedIn(virFirewall *fw, return iptablesForwardAllowRelatedIn(fw, netaddr, prefix, iface, physdev, action); =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + return virNftablesForwardAllowRelatedIn(fw, netaddr, prefix, + iface, physdev, action); + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: virNetFilterBackendUnsetError(); @@ -207,6 +230,10 @@ virNetfilterForwardAllowIn(virFirewall *fw, return iptablesForwardAllowIn(fw, netaddr, prefix, iface, physdev, action); =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + return virNftablesForwardAllowIn(fw, netaddr, prefix, + iface, physdev, action); + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: virNetFilterBackendUnsetError(); @@ -227,6 +254,10 @@ virNetfilterForwardAllowCross(virFirewall *fw, iptablesForwardAllowCross(fw, layer, iface, action); break; =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + virNftablesForwardAllowCross(fw, layer, iface, action); + break; + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: break; @@ -245,6 +276,10 @@ virNetfilterForwardRejectOut(virFirewall *fw, iptablesForwardRejectOut(fw, layer, iface, action); break; =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + virNftablesForwardRejectOut(fw, layer, iface, action); + break; + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: break; @@ -263,6 +298,10 @@ virNetfilterForwardRejectIn(virFirewall *fw, iptablesForwardRejectIn(fw, layer, iface, action); break; =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + virNftablesForwardRejectIn(fw, layer, iface, action); + break; + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: break; @@ -285,6 +324,11 @@ virNetfilterForwardMasquerade(virFirewall *fw, return iptablesForwardMasquerade(fw, netaddr, prefix, physdev, addr, port, protocol, action); =20 + + case VIR_FIREWALL_BACKEND_NFTABLES: + return virNftablesForwardMasquerade(fw, netaddr, prefix, physdev, + addr, port, protocol, action); + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: virNetFilterBackendUnsetError(); @@ -307,6 +351,10 @@ virNetfilterForwardDontMasquerade(virFirewall *fw, return iptablesForwardDontMasquerade(fw, netaddr, prefix, physdev, destaddr, action); =20 + case VIR_FIREWALL_BACKEND_NFTABLES: + return virNftablesForwardDontMasquerade(fw, netaddr, prefix, + physdev, destaddr, action); + case VIR_FIREWALL_BACKEND_UNSET: case VIR_FIREWALL_BACKEND_LAST: virNetFilterBackendUnsetError(); diff --git a/src/util/virnftables.c b/src/util/virnftables.c new file mode 100644 index 0000000000..b43b14bb82 --- /dev/null +++ b/src/util/virnftables.c @@ -0,0 +1,594 @@ +/* + * virnftables.c: helper APIs for managing nftables filter rules + * + * Copyright (C) 2023 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#include + +#include +#include +#include +#include +#include + +#include "internal.h" +#include "virnetfilter.h" +#include "virnftables.h" +#include "virfirewalld.h" +#include "vircommand.h" +#include "viralloc.h" +#include "virerror.h" +#include "virfile.h" +#include "virlog.h" +#include "virthread.h" +#include "virstring.h" +#include "virutil.h" +#include "virhash.h" + +VIR_LOG_INIT("util.nftables"); + +#define VIR_FROM_THIS VIR_FROM_NONE + +#define VIR_NFTABLES_PRIVATE_TABLE "libvirt" + +/* nftables backend uses the same binary (nft) for all layers, but + * IPv4 and IPv6 have their rules in separate classes of tables, + * either "ip" or "ip6". (there is also an "inet" class of tables that + * would examined for both IPv4 and IPv6 traffic, but since we want + * different rules for each family, we only use the family-specific + * table classes). + */ +VIR_ENUM_DECL(virNftablesLayer); +VIR_ENUM_IMPL(virNftablesLayer, + VIR_FIREWALL_LAYER_LAST, + "", + "ip", + "ip6", +); + + +VIR_ENUM_DECL(virNftablesAction); +VIR_ENUM_IMPL(virNftablesAction, + VIR_FIREWALL_ACTION_LAST, + "insert", + "append", + "delete", +); + + +int +virNftablesApplyFirewallRule(virFirewall *firewall G_GNUC_UNUSED, + virFirewallRule *rule, + char **output) +{ + size_t count =3D virFirewallRuleGetArgCount(rule); + g_autoptr(virCommand) cmd =3D NULL; + g_autofree char *cmdStr =3D NULL; + g_autofree char *error =3D NULL; + size_t i; + int status; + + if (count =3D=3D 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Can't apply empty firewall command")); + return -1; + } + + cmd =3D virCommandNew(NFT); + + for (i =3D 0; i < count; i++) + virCommandAddArg(cmd, virFirewallRuleGetArg(rule, i)); + + cmdStr =3D virCommandToString(cmd, false); + VIR_INFO("Applying rule '%s'", NULLSTR(cmdStr)); + + virCommandSetOutputBuffer(cmd, output); + virCommandSetErrorBuffer(cmd, &error); + + if (virCommandRun(cmd, &status) < 0) + return -1; + + if (status !=3D 0) { + if (STREQ_NULLABLE(virFirewallRuleGetArg(rule, 0), "list")) { + /* nft returns error status when the target of a "list" + * command doesn't exist, but we always want to just have + * an empty result, so this is not actually an error. + */ + } else if (virFirewallRuleGetIgnoreErrors(rule)) { + VIR_DEBUG("Ignoring error running command"); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to apply firewall command '%1$s': %2$= s"), + NULLSTR(cmdStr), NULLSTR(error)); + VIR_FREE(*output); + return -1; + } + } + + return 0; +} + + +typedef struct { + const char *parent; + const char *child; + const char *extraArgs; +} virNftablesGlobalChain; + +typedef struct { + virFirewallLayer layer; + virNftablesGlobalChain *chains; + size_t nchains; + bool *changed; +} virNftablesGlobalChainData; + + +static int +virNftablesPrivateChainCreate(virFirewall *fw, + virFirewallLayer layer, + const char *const *lines, + void *opaque) +{ + virNftablesGlobalChainData *data =3D opaque; + g_autoptr(GHashTable) chains =3D virHashNew(NULL); + g_autoptr(GHashTable) links =3D virHashNew(NULL); + const char *const *line; + const char *chain =3D NULL; + size_t i; + bool tableMatch =3D false; + const char *layerStr =3D virNftablesLayerTypeToString(layer); + g_autofree char *tableStr =3D g_strdup_printf("table %s libvirt {", + virNftablesLayerTypeToStri= ng(layer)); + line =3D lines; + while (line && *line) { + const char *pos =3D *line; + + virSkipSpaces(&pos); + if (STREQ(pos, tableStr)) { + /* "table ip libvirt {" */ + + tableMatch =3D true; + + } else if (STRPREFIX(pos, "chain ")) { + /* "chain LIBVIRT_OUT {" */ + + chain =3D pos + 6; + pos =3D strchr(chain, ' '); + if (pos) { + *(char *)pos =3D '\0'; + if (virHashUpdateEntry(chains, chain, (void *)0x1) < 0) + return -1; + } + + } else if ((pos =3D strstr(pos, "jump "))) { + /* "counter packets 20189046 bytes 3473108889 jump LIBVIRT_OUT= " */ + + pos +=3D 5; + if (chain) { + if (virHashUpdateEntry(links, pos, (char *)chain) < 0) + return -1; + } + + } + line++; + } + + if (!tableMatch) { + virFirewallAddRule(fw, layer, "add", "table", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, NULL); + } + + for (i =3D 0; i < data->nchains; i++) { + if (!(tableMatch && virHashLookup(chains, data->chains[i].child)))= { + virFirewallAddRule(fw, layer, "add", "chain", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + data->chains[i].child, + data->chains[i].extraArgs, NULL); + *data->changed =3D true; + } + + if (data->chains[i].parent) { + const char *from =3D virHashLookup(links, data->chains[i].chil= d); + + if (!from || STRNEQ(from, data->chains[i].parent)) { + virFirewallAddRule(fw, layer, "insert", "rule", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + data->chains[i].parent, "counter", + "jump", data->chains[i].child, NULL); + } + } + } + + return 0; +} + + +int +virNftablesSetupPrivateChains(virFirewallLayer layer) +{ + bool changed =3D false; + virNftablesGlobalChain chains[] =3D { + /* chains for filter rules */ + {NULL, "INPUT", "{ type filter hook input priority 0; policy accep= t; }"}, + {NULL, "FORWARD", "{ type filter hook forward priority 0; policy a= ccept; }"}, + {NULL, "OUTPUT", "{ type filter hook output priority 0; policy acc= ept; }"}, + {"INPUT", VIR_NETFILTER_INPUT_CHAIN, NULL}, + {"OUTPUT", VIR_NETFILTER_OUTPUT_CHAIN, NULL}, + {"FORWARD", VIR_NETFILTER_FWD_OUT_CHAIN, NULL}, + {"FORWARD", VIR_NETFILTER_FWD_IN_CHAIN, NULL}, + {"FORWARD", VIR_NETFILTER_FWD_X_CHAIN, NULL}, + + /* chains for NAT rules */ + {NULL, "POSTROUTING", "{ type nat hook postrouting priority 100; p= olicy accept; }"}, + {"POSTROUTING", VIR_NETFILTER_NAT_POSTROUTE_CHAIN, NULL}, + }; + virNftablesGlobalChainData data =3D { layer, chains, G_N_ELEMENTS(cha= ins), &changed }; + + g_autoptr(virFirewall) fw =3D virFirewallNew(VIR_FIREWALL_BACKEND_NFTA= BLES); + const char *layerStr =3D virNftablesLayerTypeToString(layer); + + virFirewallStartTransaction(fw, 0); + + /* the output of "nft list table ip[6] libvirt" will be parsed by + * the callback virNftablesPrivateChainCreate which will add any + * needed commands to add missing chains (or possibly even add the + * "ip[6] libvirt" table itself + */ + virFirewallAddRuleFull(fw, layer, false, + virNftablesPrivateChainCreate, &data, + "list", "table", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, NULL); + + if (virFirewallApply(fw) < 0) + return -1; + + return changed ? 1 : 0; +} + + +void +virNftablesInput(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + int port, + virFirewallAction action, + int tcp) +{ + g_autofree char *portstr =3D g_strdup_printf("%d", port); + const char *layerStr =3D virNftablesLayerTypeToString(layer); + + virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_INPUT_CHAIN, + "iifname", iface, + tcp ? "tcp" : "udp", + "dport", portstr, + "counter", "accept", + NULL); +} + +void +virNftablesOutput(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + int port, + virFirewallAction action, + int tcp) +{ + g_autofree char *portstr =3D g_strdup_printf("%d", port); + const char *layerStr =3D virNftablesLayerTypeToString(layer); + + virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_OUTPUT_CHAIN, + "oifname", iface, + tcp ? "tcp" : "udp", + "dport", portstr, + "counter", "accept", + NULL); +} + + +/* Allow all traffic coming from the bridge, with a valid network address + * to proceed to WAN + */ +int +virNftablesForwardAllowOut(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *iface, + const char *physdev, + virFirewallAction action) +{ + g_autofree char *networkstr =3D NULL; + virFirewallLayer layer =3D VIR_SOCKET_ADDR_FAMILY(netaddr) =3D=3D AF_I= NET ? + VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6; + const char *layerStr =3D virNftablesLayerTypeToString(layer); + virFirewallRule *rule; + + if (!(networkstr =3D virSocketAddrFormatWithPrefix(netaddr, prefix, tr= ue))) + return -1; + + rule =3D virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule= ", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_FWD_OUT_CHAIN, + layerStr, "saddr", networkstr, + "iifname", iface, NULL); + + if (physdev && physdev[0]) + virFirewallRuleAddArgList(fw, rule, "oifname", physdev, NULL); + + virFirewallRuleAddArgList(fw, rule, "counter", "accept", NULL); + + return 0; +} + + +/* Allow all traffic destined to the bridge, with a valid network address + * and associated with an existing connection + */ +int +virNftablesForwardAllowRelatedIn(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *iface, + const char *physdev, + virFirewallAction action) +{ + virFirewallLayer layer =3D VIR_SOCKET_ADDR_FAMILY(netaddr) =3D=3D AF_I= NET ? + VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6; + const char *layerStr =3D virNftablesLayerTypeToString(layer); + g_autofree char *networkstr =3D NULL; + virFirewallRule *rule; + + if (!(networkstr =3D virSocketAddrFormatWithPrefix(netaddr, prefix, tr= ue))) + return -1; + + rule =3D virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule= ", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_FWD_IN_CHAIN, NULL); + + if (physdev && physdev[0]) + virFirewallRuleAddArgList(fw, rule, "iifname", physdev, NULL); + + virFirewallRuleAddArgList(fw, rule, "oifname", iface, + layerStr, "daddr", networkstr, + "ct", "state", "related,established", + "counter", "accept", NULL); + return 0; +} + + +/* Allow all traffic destined to the bridge, with a valid network address + */ +int +virNftablesForwardAllowIn(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *iface, + const char *physdev, + virFirewallAction action) +{ + virFirewallLayer layer =3D VIR_SOCKET_ADDR_FAMILY(netaddr) =3D=3D AF_I= NET ? + VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6; + const char *layerStr =3D virNftablesLayerTypeToString(layer); + g_autofree char *networkstr =3D NULL; + virFirewallRule *rule; + + if (!(networkstr =3D virSocketAddrFormatWithPrefix(netaddr, prefix, tr= ue))) + return -1; + + rule =3D virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule= ", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_FWD_IN_CHAIN, + layerStr, "daddr", networkstr, NULL); + + if (physdev && physdev[0]) + virFirewallRuleAddArgList(fw, rule, "iifname", physdev, NULL); + + virFirewallRuleAddArgList(fw, rule, "oifname", iface, + "counter", "accept", NULL); + return 0; +} + + +void +virNftablesForwardAllowCross(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + virFirewallAction action) +{ + const char *layerStr =3D virNftablesLayerTypeToString(layer); + + virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_FWD_X_CHAIN, + "iifname", iface, + "oifname", iface, + "counter", "accept", + NULL); +} + + +void +virNftablesForwardRejectOut(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + virFirewallAction action) +{ + const char *layerStr =3D virNftablesLayerTypeToString(layer); + + virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_FWD_OUT_CHAIN, + "iifname", iface, + "counter", "reject", + NULL); +} + + +void +virNftablesForwardRejectIn(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + virFirewallAction action) +{ + const char *layerStr =3D virNftablesLayerTypeToString(layer); + + virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_FWD_IN_CHAIN, + "oifname", iface, + "counter", "reject", + NULL); +} + + +/* Masquerade all traffic coming from the network associated + * with the bridge + */ +int +virNftablesForwardMasquerade(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *physdev, + virSocketAddrRange *addr, + virPortRange *port, + const char *protocol, + virFirewallAction action) +{ + g_autofree char *networkstr =3D NULL; + g_autofree char *addrStartStr =3D NULL; + g_autofree char *addrEndStr =3D NULL; + g_autofree char *portRangeStr =3D NULL; + g_autofree char *natRangeStr =3D NULL; + virFirewallRule *rule; + int af =3D VIR_SOCKET_ADDR_FAMILY(netaddr); + virFirewallLayer layer =3D af =3D=3D AF_INET ? + VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6; + const char *layerStr =3D virNftablesLayerTypeToString(layer); + + if (!(networkstr =3D virSocketAddrFormatWithPrefix(netaddr, prefix, tr= ue))) + return -1; + + if (VIR_SOCKET_ADDR_IS_FAMILY(&addr->start, af)) { + if (!(addrStartStr =3D virSocketAddrFormat(&addr->start))) + return -1; + if (VIR_SOCKET_ADDR_IS_FAMILY(&addr->end, af)) { + if (!(addrEndStr =3D virSocketAddrFormat(&addr->end))) + return -1; + } + } + + rule =3D virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule= ", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_NAT_POSTROUTE_CHAIN, NULL); + + if (protocol && protocol[0]) { + virFirewallRuleAddArgList(fw, rule, + layerStr, "protocol", protocol, NULL); + } + + virFirewallRuleAddArgList(fw, rule, + layerStr, "saddr", networkstr, + layerStr, "daddr", "!=3D", networkstr, NULL); + + if (physdev && physdev[0]) + virFirewallRuleAddArgList(fw, rule, "oifname", physdev, NULL); + + if (protocol && protocol[0]) { + if (port->start =3D=3D 0 && port->end =3D=3D 0) { + port->start =3D 1024; + port->end =3D 65535; + } + + if (port->start < port->end && port->end < 65536) { + portRangeStr =3D g_strdup_printf(":%u-%u", port->start, port->= end); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid port range '%1$u-%2$u'."), + port->start, port->end); + return -1; + } + } + + /* Use snat if public address is specified */ + if (addrStartStr && addrStartStr[0]) { + if (addrEndStr && addrEndStr[0]) { + natRangeStr =3D g_strdup_printf("%s-%s%s", addrStartStr, addrE= ndStr, + portRangeStr ? portRangeStr : ""= ); + } else { + natRangeStr =3D g_strdup_printf("%s%s", addrStartStr, + portRangeStr ? portRangeStr : ""= ); + } + + virFirewallRuleAddArgList(fw, rule, "counter", "snat", "to", natRa= ngeStr, NULL); + } else { + virFirewallRuleAddArgList(fw, rule, "counter", "masquerade", NULL); + + if (portRangeStr && portRangeStr[0]) + virFirewallRuleAddArgList(fw, rule, "to", portRangeStr, NULL); + } + + return 0; +} + + +/* Don't masquerade traffic coming from the network associated with the br= idge + * if said traffic targets @destaddr. + */ +int +virNftablesForwardDontMasquerade(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *physdev, + const char *destaddr, + virFirewallAction action) +{ + g_autofree char *networkstr =3D NULL; + virFirewallLayer layer =3D VIR_SOCKET_ADDR_FAMILY(netaddr) =3D=3D AF_I= NET ? + VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6; + const char *layerStr =3D virNftablesLayerTypeToString(layer); + virFirewallRule *rule; + + if (!(networkstr =3D virSocketAddrFormatWithPrefix(netaddr, prefix, tr= ue))) + return -1; + + rule =3D virFirewallAddRule(fw, layer, + virNftablesActionTypeToString(action), "rule= ", + layerStr, VIR_NFTABLES_PRIVATE_TABLE, + VIR_NETFILTER_NAT_POSTROUTE_CHAIN, NULL); + + if (physdev && physdev[0]) + virFirewallRuleAddArgList(fw, rule, "oifname", physdev, NULL); + + virFirewallRuleAddArgList(fw, rule, + layerStr, "saddr", networkstr, + layerStr, "daddr", destaddr, + "counter", "return", NULL); + return 0; +} diff --git a/src/util/virnftables.h b/src/util/virnftables.h new file mode 100644 index 0000000000..5ea0f2452f --- /dev/null +++ b/src/util/virnftables.h @@ -0,0 +1,118 @@ +/* + * virnftables.h: helper APIs for managing nftables packet filters + * + * Copyright (C) 2023 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#pragma once + +#include "virsocketaddr.h" +#include "virfirewall.h" +#include "virnetfilter.h" + +/* virNftablesApplyFirewallRule should be called only from virnetfilter.c= */ + +int +virNftablesApplyFirewallRule(virFirewall *firewall, + virFirewallRule *rule, + char **output); + + +/* All the following functions can either insert or delete the given + * type of filter rule, depending on whether action is + * VIR_NETFILTER_INSERT or VIR_NETFILTER_DELETE. + */ + +int +virNftablesSetupPrivateChains(virFirewallLayer layer); + +void +virNftablesInput(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + int port, + virFirewallAction action, + int tcp); + +void +virNftablesOutput(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + int port, + virFirewallAction action, + int tcp); + +int +virNftablesForwardAllowOut(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *iface, + const char *physdev, + virFirewallAction action); + +int +virNftablesForwardAllowRelatedIn(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *iface, + const char *physdev, + virFirewallAction action); + +int +virNftablesForwardAllowIn(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *iface, + const char *physdev, + virFirewallAction action); + + +void +virNftablesForwardAllowCross(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + virFirewallAction action); + +void +virNftablesForwardRejectOut(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + virFirewallAction action); + +void +virNftablesForwardRejectIn(virFirewall *fw, + virFirewallLayer layer, + const char *iface, + virFirewallAction action); + +int +virNftablesForwardMasquerade(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *physdev, + virSocketAddrRange *addr, + virPortRange *port, + const char *protocol, + virFirewallAction action); + +int +virNftablesForwardDontMasquerade(virFirewall *fw, + virSocketAddr *netaddr, + unsigned int prefix, + const char *physdev, + const char *destaddr, + virFirewallAction action); --=20 2.39.2