From nobody Fri Dec 19 19:02:58 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1745653485244449.1894241821418; Sat, 26 Apr 2025 00:44:45 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 8C6E0B09; Sat, 26 Apr 2025 03:44:44 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id DCBF1B3A; Sat, 26 Apr 2025 03:43:36 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 2CAD19BB; Sat, 26 Apr 2025 03:43:32 -0400 (EDT) Received: from mail-ed1-f50.google.com (mail-ed1-f50.google.com [209.85.208.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 56D87A13 for ; Sat, 26 Apr 2025 03:43:31 -0400 (EDT) Received: by mail-ed1-f50.google.com with SMTP id 4fb4d7f45d1cf-5e5bc066283so5237907a12.0 for ; Sat, 26 Apr 2025 00:43:31 -0700 (PDT) Received: from tulp.dynamic.ziggo.nl (80-115-115-199.cable.dynamic.v4.ziggo.nl. [80.115.115.199]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ace6ed70606sm248728566b.160.2025.04.26.00.43.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 26 Apr 2025 00:43:28 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=DKIM_ADSP_CUSTOM_MED, DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=3.4.4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1745653410; x=1746258210; darn=lists.libvirt.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=CVD+E75I9n7Y2JfhVL5wKTdKSwmZ9tYnFXjcd0SJARU=; b=GeQv5SEFxeVUGZNN2PCV1HEI9vPVUTDkMGlfzpQrhEOp7MOCG8GjCvm3cwZRSwAsXV Id9Hc4difSXAchKE2+OGlIIpKDkAyFChqyMod4Km44cw1krNx8GPeo2PgNcTaz7Sld4J Uc4xEqUnGcrmUsfwIbCFHYJzZebkBY0PJf0PXgQJIOy5PebVREmLoUCSlo+RHbzpLXxO mpFul89n5ElCbc4xD5K6gJtL9jzFg2HywBI1s+9Si8ClKRuDEH6nJCy6IeprZmLX3WHf hUKueWqL3yM17lak+Xy5HLm2iIaWYN+U9eOzin/odk0tNHEmRK9WhsJZtfereHakYD9L y+ZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1745653410; x=1746258210; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=CVD+E75I9n7Y2JfhVL5wKTdKSwmZ9tYnFXjcd0SJARU=; b=OUJEktRHfJ7JKGKFxwbuUBzSMiL89RGTwWxln/PwJorl1pzWJ+Wa+sranzjnJwyJCi je98R5l1Nn8iZpBElxXwW2V2BRZoYXspL3PbOcL6eC6XuyDgGn4eIkGpGiYHRyRReL46 2a/AObE4Tt+y5WSW8gSDJhmAl0bWj8zJv10NiHtWuUj9cW7eTaIvPxoq85Wp1h+5qokg JZS+q9BW0QORQXb6zdA3IluaQRnXQgxP/OT2kpsawdE0BCDHEuQLaWLKRIuP6h0YOIsn sh5+sq2XP0mhoDLo4esUhuYOp+ubC80PMi905FWk9I0ZxUAL2v3Sb3L2u295doQaRUAp WcnQ== X-Gm-Message-State: AOJu0Yx+qkUk55PzwZ5aPg1qKQ0QcWotXPWOYFNNAs8gvdgHVJA7oE2W w1yiiIg/qV8mwNO0aVvfvwwqoKygAj5bzfXPQmdP5+Xfth7fRtH4acN02lc9ySA= X-Gm-Gg: ASbGncvBUjKVyQdntfiCL+fI/JgDUUz9Adx+g+dbCH3vnxwmBz0UENN5eIBVn2Fz6fo /ahLX+lTkvjZZpFNtMQlQDwH1Xz06WP9mJz36WMVgJSum+fAMY+Cm8ip3FyYb6DAjohy3hE8axW 7624TeXzex3x+SJW+n2v/gjnZN8nnUk2vsU4xQjQYc+sOg3atb0l6sGkof0wdpzFMnS52l7p6Vv nKMI6Y5iBY9cEzLHHMaWnaCrRYENI7ts2vVHL7KSXTCO6tEAnspBar5VHN2zyYWW4LuhV+pXxNC GJtUKHtlKV/umuOs3rLgSf61IHzUGTPiCu3NrldNsaq4wTZvmZp+idS63wsckv+bqOhvVVz4/8R QePqsqyyzvIPZv3X5iUdP9Ak= X-Google-Smtp-Source: AGHT+IFDps8J9ylZSnFiBWyYxbnSD01xsTfle+CHgFUf1yqd35LBMwluj6OzBlMX90ZM/s4ZIOf9BA== X-Received: by 2002:a17:907:9719:b0:ace:6bfb:4a11 with SMTP id a640c23a62f3a-ace710951aemr373511266b.24.1745653409318; Sat, 26 Apr 2025 00:43:29 -0700 (PDT) From: Roman Bogorodskiy To: devel@lists.libvirt.org Subject: [PATCH 1/2] network: bridge_driver: add BSD implementation Date: Sat, 26 Apr 2025 09:42:35 +0200 Message-ID: <20250426074251.20557-2-bogorodskiy@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250426074251.20557-1-bogorodskiy@gmail.com> References: <20250426074251.20557-1-bogorodskiy@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: 45ZRLGTCVBH7VUUCF4FG5SJHHSSKBQNP X-Message-ID-Hash: 45ZRLGTCVBH7VUUCF4FG5SJHHSSKBQNP X-MailFrom: bogorodskiy@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header CC: Roman Bogorodskiy X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1745653487408019000 Content-Type: text/plain; charset="utf-8" Add BSD-specific platform flavor of the bridge driver which will be used as a base for Packet Filter (pf) based NAT networking implementation. Signed-off-by: Roman Bogorodskiy --- po/POTFILES | 1 + src/network/bridge_driver_bsd.c | 101 +++++++++++++++++++++++++++ src/network/bridge_driver_platform.c | 2 + 3 files changed, 104 insertions(+) create mode 100644 src/network/bridge_driver_bsd.c diff --git a/po/POTFILES b/po/POTFILES index 9747c38951..90664fe6e7 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -145,6 +145,7 @@ src/lxc/lxc_hostdev.c src/lxc/lxc_native.c src/lxc/lxc_process.c src/network/bridge_driver.c +src/network/bridge_driver_bsd.c src/network/bridge_driver_conf.c src/network/bridge_driver_linux.c src/network/bridge_driver_nop.c diff --git a/src/network/bridge_driver_bsd.c b/src/network/bridge_driver_bs= d.c new file mode 100644 index 0000000000..93312fe6db --- /dev/null +++ b/src/network/bridge_driver_bsd.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2025 FreeBSD Foundation + * + * 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 "virlog.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +VIR_LOG_INIT("network.bridge_driver_bsd"); + +static virErrorPtr errInitV4; +static virErrorPtr errInitV6; + +void networkPreReloadFirewallRules(virNetworkDriverState *driver G_GNUC_UN= USED, + bool startup G_GNUC_UNUSED, + bool force G_GNUC_UNUSED) +{ +} + + +void networkPostReloadFirewallRules(bool startup G_GNUC_UNUSED) +{ +} + + +int networkCheckRouteCollision(virNetworkDef *def G_GNUC_UNUSED) +{ + return 0; +} + +int networkAddFirewallRules(virNetworkDef *def G_GNUC_UNUSED, + virFirewallBackend firewallBackend, + virFirewall **fwRemoval G_GNUC_UNUSED) +{ + if (def->forward.type =3D=3D VIR_NETWORK_FORWARD_OPEN) { + VIR_DEBUG("No firewall rules to add for mode=3D'open' network '%s'= ", def->name); + } else { + VIR_DEBUG("Adding firewall rules for mode=3D'%s' network '%s' usin= g %s", + virNetworkForwardTypeToString(def->forward.type), + def->name, + virFirewallBackendTypeToString(firewallBackend)); + + if (errInitV4 && + (virNetworkDefGetIPByIndex(def, AF_INET, 0) || + virNetworkDefGetRouteByIndex(def, AF_INET, 0))) { + virSetError(errInitV4); + return -1; + } + + if (errInitV6 && + (virNetworkDefGetIPByIndex(def, AF_INET6, 0) || + virNetworkDefGetRouteByIndex(def, AF_INET6, 0) || + def->ipv6nogw)) { + virSetError(errInitV6); + return -1; + } + + /* now actually add the rules */ + switch (firewallBackend) { + case VIR_FIREWALL_BACKEND_NONE: + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("No firewall backend is available")); + return -1; + + case VIR_FIREWALL_BACKEND_IPTABLES: + case VIR_FIREWALL_BACKEND_NFTABLES: + case VIR_FIREWALL_BACKEND_LAST: + virReportEnumRangeError(virFirewallBackend, firewallBackend); + return -1; + } + } + return 0; +} + +void +networkRemoveFirewallRules(virNetworkObj *obj, + bool unsetZone G_GNUC_UNUSED) +{ + virNetworkDef *def =3D virNetworkObjGetDef(obj); + if (def->forward.type =3D=3D VIR_NETWORK_FORWARD_OPEN) { + VIR_DEBUG("No firewall rules to remove for mode=3D'open' network '= %s'", + def->name); + return; + } +} diff --git a/src/network/bridge_driver_platform.c b/src/network/bridge_driv= er_platform.c index 9ddcb71063..42fbcdbc0b 100644 --- a/src/network/bridge_driver_platform.c +++ b/src/network/bridge_driver_platform.c @@ -25,6 +25,8 @@ =20 #if defined(__linux__) # include "bridge_driver_linux.c" +#elif defined(__FreeBSD__) +# include "bridge_driver_bsd.c" #else # include "bridge_driver_nop.c" #endif --=20 2.49.0 From nobody Fri Dec 19 19:02:58 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 174565354362985.45902867511018; Sat, 26 Apr 2025 00:45:43 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id A277FA3D; Sat, 26 Apr 2025 03:45:42 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id F010CBC5; Sat, 26 Apr 2025 03:43:38 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id D83629BB; Sat, 26 Apr 2025 03:43:33 -0400 (EDT) Received: from mail-ej1-f47.google.com (mail-ej1-f47.google.com [209.85.218.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 771B5A11 for ; Sat, 26 Apr 2025 03:43:32 -0400 (EDT) Received: by mail-ej1-f47.google.com with SMTP id a640c23a62f3a-acb2faa9f55so376274766b.3 for ; Sat, 26 Apr 2025 00:43:32 -0700 (PDT) Received: from tulp.dynamic.ziggo.nl (80-115-115-199.cable.dynamic.v4.ziggo.nl. [80.115.115.199]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ace6ed70606sm248728566b.160.2025.04.26.00.43.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 26 Apr 2025 00:43:29 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=DKIM_ADSP_CUSTOM_MED, DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=3.4.4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1745653410; x=1746258210; darn=lists.libvirt.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=sBNTRteNyXvNXBiaqwdvvxeE/0GhwBwewy4+OR1xH7g=; b=C14+kvDgNK2zwsBx/pqgoUvAeZIEL+hKkZ2ezvfW64lBf9aUC9dKuKqSAX3rnajGFP nfCz9mdOaPC/75SP3GyOoEK2svNdumZXbJbIOzEUmhV7YkTkJr54oFq/APDy42I8rYnj v5sXUIrLnVVLYmxTzW0YhWN2oSPTbPEJ8eYwLVuWu6LkFlJ0p7V0ys0HLbHiadpmf8qA HjLbL5GLUZRo25fDtrGEwy9MokiFEYsIWeAQlurpCNaHqzse+V9kUSQtA4sp/ootuCeU 82yX4joG8yT/UoBPgRV8AzXC1seswIn70VL3AgekoLpkhbbwOEYVPujyCT6ksUdwlyIo 3FyQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1745653410; x=1746258210; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=sBNTRteNyXvNXBiaqwdvvxeE/0GhwBwewy4+OR1xH7g=; b=wobrLKB2vmwFbC381LhmFkN7lUoHM4xpV2CU/LSflWjpUww92wptK+O+PRut3+FAnl HrfLnCameadWR+Asz4H9Il8qrtZoE2v0wbQbH/Szmuv3dE9xBu1J/7dWsN7+nZZJwQlY WZ8Uu6qLp/x3XD4GzFyq/+zh16az6J8RR6cILXF/dEX5rL6bqnEJCUSqz6rLJVgmcR87 jqnjVKbSvpzkyAcunXFjf9escZ7BRr8HnfkiJV3n3rXnSMGsz2kz3q600fhTlwn0Crd8 DanxDd4TO2sugepti7dK9fX1kXjehPJGR8HiupUP/I7uqvcbp0jDppbjys3/uieSKwyM 5wkA== X-Gm-Message-State: AOJu0YwRoLxFoHU/eDKL3iJFdet0rWFcFOwLz48sT65cmEi8+sdqu/z0 Q3V1jJoy7PqHbmu8I+Jq07JSjdPZMxPGhbNYrtEQCm9lhfyfgnTv7nVjxxm+X4o= X-Gm-Gg: ASbGncvho1ao+yqg18a7Vo2xyqk+rPoVamykpyYXJHrY52lRGDfZaLuK+n9PF21kwim ur3oo8/aYgARgVq3SeGNztqbKNpxaxSw/0K0DtCFRI3VxnmWX6JAZr83fI9sKxhf934ydW6P9so 9HqS2wXK3RI51lLp8ZBMhjxSjDjxnbrD7nBS/HN7RpRslf+5uTW9KLaOhqNtVbDffoUdeQk7T8f hM3JuJVYwesQsv7SRaHSooPVKBJACqc5m7EWFKfEr6hOFwbJX/pL7dThrCo9EnGg2z6Cz/t3V35 sXJygZ1dgaw8vVGG3Jcwh/9AGHhTbjxoJuXZoEfCobUYNlmYVABg986n9CkwG/BCzYO/iO9HM86 U0xG7dZEfVGQxzUfuihS/fBU= X-Google-Smtp-Source: AGHT+IGdf+5hWKWdCk5DmlQKP9HuDIBOOO0VWLxQrI7BL6bySdDZu3UAbj8dY1Y8a8GWzL2eELf6lw== X-Received: by 2002:a17:907:94c6:b0:aca:95e7:ec59 with SMTP id a640c23a62f3a-ace848f7819mr198040966b.19.1745653410177; Sat, 26 Apr 2025 00:43:30 -0700 (PDT) From: Roman Bogorodskiy To: devel@lists.libvirt.org Subject: [PATCH 2/2] network: introduce Packet Filter firewall backend Date: Sat, 26 Apr 2025 09:42:36 +0200 Message-ID: <20250426074251.20557-3-bogorodskiy@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250426074251.20557-1-bogorodskiy@gmail.com> References: <20250426074251.20557-1-bogorodskiy@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: QRZ7QSCQ4D7BBJ4RGPYHFUYF3OAAN4YU X-Message-ID-Hash: QRZ7QSCQ4D7BBJ4RGPYHFUYF3OAAN4YU X-MailFrom: bogorodskiy@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header CC: Roman Bogorodskiy X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1745653546620019100 Content-Type: text/plain; charset="utf-8" Implement NAT networking support based on the Packet Filter (pf) firewall in FreeBSD. At this point, the implementation is very basic. It creates: - Essential NAT translation rules - Basic forwarding rules Implementation uses pf's anchor feature to group rules. All rules live in the "libvirt" anchor and every libvirt's network has its own sub-anchor. Currently there are some assumptions and limitations: - We assume that a user has created the "libvirt" (nat-)anchors. As they cannot be created on fly, it's better not to touch global pf configuration and let the user do the changes. If the user doesn't have these anchors configured, the rules will still be created in sub-anchors, but will not be effective until these anchors are activated. Should we check if these anchors are not active to give some runtime warning? - Currently, rule reloading is not smart: it always deletes rules, flushes rules and re-creates that. It would be better to do that more gracefully. - IPv6 configurations are currently not supported - For NAT, pf requires explicit IP address or an interface to NAT to. We try to obtain that from the network XML definition, and if it's not specified, we try to determine interface corresponding to the default route. Signed-off-by: Roman Bogorodskiy --- meson.build | 2 + po/POTFILES | 1 + src/network/bridge_driver_bsd.c | 6 + src/network/bridge_driver_conf.c | 8 + src/network/bridge_driver_linux.c | 2 + src/network/meson.build | 1 + src/network/network_pf.c | 327 ++++++++++++++++++++++++++++++ src/network/network_pf.h | 26 +++ src/util/virfirewall.c | 4 +- src/util/virfirewall.h | 2 + 10 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 src/network/network_pf.c create mode 100644 src/network/network_pf.h diff --git a/meson.build b/meson.build index 37b1caa566..b8a9be25bb 100644 --- a/meson.build +++ b/meson.build @@ -1642,6 +1642,8 @@ if not get_option('driver_network').disabled() and co= nf.has('WITH_LIBVIRTD') if firewall_backend_priority.length() =3D=3D 0 if host_machine.system() =3D=3D 'linux' firewall_backend_priority =3D ['nftables', 'iptables'] + elif host_machine.system() =3D=3D 'freebsd' + firewall_backend_priority =3D ['pf'] else # No firewall impl on non-Linux so far, so force 'none' # as placeholder diff --git a/po/POTFILES b/po/POTFILES index 90664fe6e7..dc7293d0cd 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -152,6 +152,7 @@ src/network/bridge_driver_nop.c src/network/leaseshelper.c src/network/network_iptables.c src/network/network_nftables.c +src/network/network_pf.c src/node_device/node_device_driver.c src/node_device/node_device_udev.c src/nwfilter/nwfilter_dhcpsnoop.c diff --git a/src/network/bridge_driver_bsd.c b/src/network/bridge_driver_bs= d.c index 93312fe6db..ad4fbd064b 100644 --- a/src/network/bridge_driver_bsd.c +++ b/src/network/bridge_driver_bsd.c @@ -19,6 +19,7 @@ #include =20 #include "virlog.h" +#include "network_pf.h" =20 #define VIR_FROM_THIS VIR_FROM_NONE =20 @@ -78,6 +79,9 @@ int networkAddFirewallRules(virNetworkDef *def G_GNUC_UNU= SED, _("No firewall backend is available")); return -1; =20 + case VIR_FIREWALL_BACKEND_PF: + return pfAddFirewallRules(def); + case VIR_FIREWALL_BACKEND_IPTABLES: case VIR_FIREWALL_BACKEND_NFTABLES: case VIR_FIREWALL_BACKEND_LAST: @@ -98,4 +102,6 @@ networkRemoveFirewallRules(virNetworkObj *obj, def->name); return; } + + pfRemoveFirewallRules(def); } diff --git a/src/network/bridge_driver_conf.c b/src/network/bridge_driver_c= onf.c index 738652390f..280c0f9c4f 100644 --- a/src/network/bridge_driver_conf.c +++ b/src/network/bridge_driver_conf.c @@ -129,6 +129,14 @@ virNetworkLoadDriverConfig(virNetworkDriverConfig *cfg= G_GNUC_UNUSED, break; } =20 + case VIR_FIREWALL_BACKEND_PF: { + g_autofree char *pfctlInPath =3D virFindFileInPath(PFCTL); + + if (pfctlInPath) + fwBackendSelected =3D true; + break; + } + case VIR_FIREWALL_BACKEND_LAST: virReportEnumRangeError(virFirewallBackend, fwBackends[i]); return -1; diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_= linux.c index 86f6a5915f..9077178c3e 100644 --- a/src/network/bridge_driver_linux.c +++ b/src/network/bridge_driver_linux.c @@ -58,6 +58,7 @@ networkFirewallSetupPrivateChains(virFirewallBackend back= end, case VIR_FIREWALL_BACKEND_NFTABLES: return nftablesSetupPrivateChains(layer); =20 + case VIR_FIREWALL_BACKEND_PF: case VIR_FIREWALL_BACKEND_LAST: virReportEnumRangeError(virFirewallBackend, backend); return -1; @@ -437,6 +438,7 @@ networkAddFirewallRules(virNetworkDef *def, case VIR_FIREWALL_BACKEND_NFTABLES: return nftablesAddFirewallRules(def, fwRemoval); =20 + case VIR_FIREWALL_BACKEND_PF: case VIR_FIREWALL_BACKEND_LAST: virReportEnumRangeError(virFirewallBackend, firewallBackend); return -1; diff --git a/src/network/meson.build b/src/network/meson.build index 07cd5cda55..b527bab392 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -4,6 +4,7 @@ network_driver_sources =3D [ 'bridge_driver_platform.c', 'network_iptables.c', 'network_nftables.c', + 'network_pf.c', ] =20 driver_source_files +=3D files(network_driver_sources) diff --git a/src/network/network_pf.c b/src/network/network_pf.c new file mode 100644 index 0000000000..b12ef86089 --- /dev/null +++ b/src/network/network_pf.c @@ -0,0 +1,327 @@ +/* + * network_pf.c: pf-based firewall implementation for virtual networks + * + * Copyright (C) 2025 FreeBSD Foundation + * + * 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 + * . + */ + +/* + * pf(4) configuration principles/assumptions. + * + * All libvirt-managed firewall rule are configured within a pf anchor. + * Every libvirt network has a corresponding sub-anchor, like "libvirt/$ne= twork_name". + * Libvirt does not create the root anchors, so users are expected to spec= ify them in + * their firewall configuration. Minimal configuration might look like: + * + * # cat /etc/pf.conf + * scrub all + * + * nat-anchor "libvirt\*" + * anchor "libvirt\*" + * + * pass all + * # + * + * Users are not expected to add/modify rules in the "libvirt\*" subanchor= s because + * the changes will be lost on restart. + * + * IPv6 NAT is currently not supported. + */ + +#include + +#include +#include +#include +#include +#include +#include +#ifdef WITH_NET_IF_H +# include +#endif +#if defined(__FreeBSD__) +# include +# include +#endif +#include +#include + +#include "internal.h" +#include "virfirewalld.h" +#include "vircommand.h" +#include "virerror.h" +#include "virlog.h" +#include "virhash.h" +#include "virenum.h" +#include "virstring.h" +#include "network_pf.h" + +VIR_LOG_INIT("network.pf"); + +#define VIR_FROM_THIS VIR_FROM_NONE + + +static const char networkLocalMulticastIPv4[] =3D "224.0.0.0/24"; +static const char networkLocalBroadcast[] =3D "255.255.255.255/32"; + + +static char * +findDefaultRouteInterface(void) +{ +#if defined(__FreeBSD__) + int mib[6] =3D {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_DUMP, 0}; + size_t needed; + g_autofree char *buf =3D NULL; + char *lim, *next; + struct rt_msghdr *rtm; + struct sockaddr *sa; + struct sockaddr_in *sin; + struct sockaddr_dl *sdl; + char *ifname; + size_t ifname_len; + size_t i; + + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) { + virReportSystemError(errno, + "%s", + _("Unable to get default interface name")); + return NULL; + } + + if (posix_memalign((void **)&buf, 8, needed) !=3D 0) { + virReportSystemError(errno, + "%s", + _("Unable to get default interface name")); + return NULL; + } + + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { + virReportSystemError(errno, + "%s", + _("Unable to get default interface name")); + return NULL; + } + + lim =3D buf + needed; + next =3D buf; + + while (next < lim) { + rtm =3D (struct rt_msghdr *)(void *)next; + if (next + rtm->rtm_msglen > lim) + break; + + sin =3D (struct sockaddr_in *)(rtm + 1); + + if ((rtm->rtm_flags & RTF_GATEWAY) && sin->sin_addr.s_addr =3D=3D = INADDR_ANY) { + sdl =3D NULL; + sa =3D (struct sockaddr *)(sin + 1); + + for (i =3D 1; i < RTAX_MAX; i++) { + if (rtm->rtm_addrs & (1 << i)) { + if (i =3D=3D RTAX_IFP && sa->sa_family =3D=3D AF_LINK)= { + sdl =3D (struct sockaddr_dl *)(void *)sa; + ifname_len =3D (sdl->sdl_nlen >=3D IFNAMSIZ) ? IFN= AMSIZ - 1 : sdl->sdl_nlen; + ifname =3D g_new(char, ifname_len + 1); + virStrcpy(ifname, sdl->sdl_data, ifname_len + 1); + return ifname; + } + sa =3D (struct sockaddr *)((char *)sa + + ((sa->sa_len > 0) ? sa->sa_len : sizeof(struct so= ckaddr))); + } + } + } + + next +=3D rtm->rtm_msglen; + } +#endif + + return NULL; +} + +static int +pfAddNatFirewallRules(virNetworkDef *def, + virNetworkIPDef *ipdef) +{ + /* + * # NAT rules + * table persist + * { 0.0.0.0/0, ! 192.168.122.0/24, !224.0.0.0/24, !255.255.255.255 } + * nat pass log on $ext_if from 192.168.122.0/24 to + * -> ($ext_if) port 1024:65535 + * + * # Filtering + * pass log quick on virbr0 from 192.168.122.0/24 to 192.168.122.0/24 + * pass out log quick on virbr0 from 192.168.122.0/24 to 224.0.0.0/24 + * pass out log quick on virbr0 from 192.168.122.0/24 to 255.255.255.2= 55 + * block log on virbr0 + */ + int prefix =3D virNetworkIPDefPrefix(ipdef); + g_autofree const char *forwardIf =3D g_strdup(virNetworkDefForwardIf(d= ef, 0)); + g_auto(virBuffer) pf_rules_buf =3D VIR_BUFFER_INITIALIZER; + g_autoptr(virCommand) cmd =3D virCommandNew(PFCTL); + virPortRange *portRange =3D &def->forward.port; + g_autofree char *portRangeStr =3D NULL; + + if (prefix < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid prefix or netmask for '%1$s'"), + def->bridge); + return -1; + } + + if (portRange->start =3D=3D 0 && portRange->end =3D=3D 0) { + portRange->start =3D 1024; + portRange->end =3D 65535; + } + + if (portRange->start < portRange->end && portRange->end < 65536) { + portRangeStr =3D g_strdup_printf("%u:%u", + portRange->start, + portRange->end); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid port range '%1$u-%2$u'."), + portRange->start, portRange->end); + return -1; + } + + if (!forwardIf) { + forwardIf =3D findDefaultRouteInterface(); + if (!forwardIf) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + _("Cannot determine the default interface")); + return -1; + } + } + + virBufferAsprintf(&pf_rules_buf, + "table persist { 0.0.0.0/0, ! %s/%d, ! %s, = ! %s }\n", + virSocketAddrFormat(&ipdef->address), + prefix, + networkLocalMulticastIPv4, + networkLocalBroadcast); + virBufferAsprintf(&pf_rules_buf, + "nat pass on %s from %s/%d to -> (%s) port = %s\n", + forwardIf, + virSocketAddrFormat(&ipdef->address), + prefix, + forwardIf, + portRangeStr); + virBufferAsprintf(&pf_rules_buf, + "pass quick on %s from %s/%d to %s/%d\n", + def->bridge, + virSocketAddrFormat(&ipdef->address), + prefix, + virSocketAddrFormat(&ipdef->address), + prefix); + virBufferAsprintf(&pf_rules_buf, + "pass quick on %s from %s/%d to %s\n", + def->bridge, + virSocketAddrFormat(&ipdef->address), + prefix, + networkLocalMulticastIPv4); + virBufferAsprintf(&pf_rules_buf, + "pass quick on %s from %s/%d to %s\n", + def->bridge, + virSocketAddrFormat(&ipdef->address), + prefix, + networkLocalBroadcast); + virBufferAsprintf(&pf_rules_buf, + "block on %s\n", + def->bridge); + + /* pfctl -a libvirt/default -F all -f - */ + virCommandAddArg(cmd, "-a"); + virCommandAddArgFormat(cmd, "libvirt/%s", def->name); + virCommandAddArgList(cmd, "-F", "all", "-f", "-", NULL); + + virCommandSetInputBuffer(cmd, virBufferContentAndReset(&pf_rules_buf)); + + if (virCommandRun(cmd, NULL) < 0) { + VIR_WARN("Failed to create firewall rules for network %s", + def->name); + return -1; + } + return 0; +} + + +static int +pfAddRoutingFirewallRules(virNetworkDef *def, + virNetworkIPDef *ipdef G_GNUC_UNUSED) +{ + int prefix =3D virNetworkIPDefPrefix(ipdef); + + if (prefix < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid prefix or netmask for '%1$s'"), + def->bridge); + return -1; + } + + /* TODO: routing rules */ + + return 0; +} + + +static int +pfAddIPSpecificFirewallRules(virNetworkDef *def, + virNetworkIPDef *ipdef) +{ + if (def->forward.type =3D=3D VIR_NETWORK_FORWARD_NAT) { + if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) + return pfAddNatFirewallRules(def, ipdef); + else + return -1; + } else if (def->forward.type =3D=3D VIR_NETWORK_FORWARD_ROUTE) { + return pfAddRoutingFirewallRules(def, ipdef); + } + return 0; +} + + +int +pfAddFirewallRules(virNetworkDef *def) +{ + size_t i; + virNetworkIPDef *ipdef; + + for (i =3D 0; + (ipdef =3D virNetworkDefGetIPByIndex(def, AF_UNSPEC, i)); + i++) { + if (pfAddIPSpecificFirewallRules(def, ipdef) < 0) + return -1; + } + + return 0; +} + + +void +pfRemoveFirewallRules(virNetworkDef *def) +{ + /* pfctl -a libvirt/default -F all */ + g_autoptr(virCommand) cmd =3D virCommandNew(PFCTL); + virCommandAddArg(cmd, "-a"); + virCommandAddArgFormat(cmd, "libvirt/%s", def->name); + virCommandAddArgList(cmd, "-F", "all", NULL); + + if (virCommandRun(cmd, NULL) < 0) + VIR_WARN("Failed to remove firewall rules for network %s", + def->name); +} diff --git a/src/network/network_pf.h b/src/network/network_pf.h new file mode 100644 index 0000000000..2cf5a1a6d9 --- /dev/null +++ b/src/network/network_pf.h @@ -0,0 +1,26 @@ +/* + * network_pf.h: helper APIs for managing pf in network driver + * + * Copyright (C) 2025 FreeBSD Foundation + * + * 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 "network_conf.h" + +int pfAddFirewallRules(virNetworkDef *def); +void pfRemoveFirewallRules(virNetworkDef *def); diff --git a/src/util/virfirewall.c b/src/util/virfirewall.c index 9389bcf541..69521e2b46 100644 --- a/src/util/virfirewall.c +++ b/src/util/virfirewall.c @@ -39,7 +39,8 @@ VIR_ENUM_IMPL(virFirewallBackend, VIR_FIREWALL_BACKEND_LAST, "none", "iptables", - "nftables"); + "nftables", + "pf"); =20 VIR_ENUM_DECL(virFirewallLayer); VIR_ENUM_IMPL(virFirewallLayer, @@ -847,6 +848,7 @@ virFirewallApplyCmd(virFirewall *firewall, return -1; break; =20 + case VIR_FIREWALL_BACKEND_PF: case VIR_FIREWALL_BACKEND_LAST: default: virReportEnumRangeError(virFirewallBackend, diff --git a/src/util/virfirewall.h b/src/util/virfirewall.h index d42e60884b..030cb2c0a4 100644 --- a/src/util/virfirewall.h +++ b/src/util/virfirewall.h @@ -30,6 +30,7 @@ #define IPTABLES "iptables" #define IP6TABLES "ip6tables" #define NFT "nft" +#define PFCTL "pfctl" =20 typedef struct _virFirewall virFirewall; =20 @@ -48,6 +49,7 @@ typedef enum { VIR_FIREWALL_BACKEND_NONE, /* Always fails */ VIR_FIREWALL_BACKEND_IPTABLES, VIR_FIREWALL_BACKEND_NFTABLES, + VIR_FIREWALL_BACKEND_PF, =20 VIR_FIREWALL_BACKEND_LAST, } virFirewallBackend; --=20 2.49.0