summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2021-05-14 11:41:43 +0200
committerThomas Haller <thaller@redhat.com>2021-05-14 11:41:43 +0200
commit6f04f5bc2fe242fc24d476f32507cc380ae57934 (patch)
treea732ee942ab87cebf9ae3ad2dd8c5b174e2231a1
parent63bb7580bc9fd4e0391917c7aff09a7ed0e55cd7 (diff)
parenta79d5e2218d4adc40cb8794b56845b06cb5a2478 (diff)
downloadNetworkManager-6f04f5bc2fe242fc24d476f32507cc380ae57934.tar.gz
firewall: merge branch 'th/firewall-nft'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/847
-rw-r--r--config.h.meson5
-rw-r--r--configure.ac23
-rw-r--r--man/NetworkManager.conf.xml18
-rw-r--r--meson.build5
-rw-r--r--meson_options.txt1
-rw-r--r--src/core/nm-config-data.h82
-rw-r--r--src/core/nm-firewall-utils.c457
-rw-r--r--src/core/nm-firewall-utils.h11
-rw-r--r--src/core/tests/test-core.c21
-rw-r--r--src/libnm-base/nm-config-base.h3
-rw-r--r--src/libnm-glib-aux/nm-io-utils.c73
-rw-r--r--src/libnm-glib-aux/nm-io-utils.h2
-rw-r--r--src/libnm-glib-aux/nm-macros-internal.h7
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h13
14 files changed, 664 insertions, 57 deletions
diff --git a/config.h.meson b/config.h.meson
index a911dbe257..432402d5b4 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -67,7 +67,10 @@
/* Define to path of iptables binary */
#mesondefine IPTABLES_PATH
-/* Define to path to the Jansson shared library */
+/* Define to path of nft binary */
+#mesondefine NFT_PATH
+
+//* Define to path to the Jansson shared library */
#mesondefine JANSSON_SONAME
/* Define to path of the kernel firmware directory */
diff --git a/configure.ac b/configure.ac
index e27ceeb7a1..d23d315cf8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -943,13 +943,12 @@ fi
AC_DEFINE_UNQUOTED(NM_CONFIG_DEFAULT_MAIN_RC_MANAGER, "$config_dns_rc_manager_default", [Default value for main.rc-manager setting (--with-config-dns-rc-manager-default)])
AC_SUBST(NM_CONFIG_DEFAULT_MAIN_RC_MANAGER, $config_dns_rc_manager_default)
-# iptables path
AC_ARG_WITH(iptables,
- AS_HELP_STRING([--with-iptables=/path/to/iptables], [path to iptables]))
+ AS_HELP_STRING([--with-iptables=/usr/sbin/iptables], [path to iptables]))
if test "x${with_iptables}" = x; then
AC_PATH_PROG(IPTABLES_PATH, iptables, [], $PATH:/sbin:/usr/sbin)
- if ! test -x "$IPTABLES_PATH"; then
- AC_MSG_ERROR(iptables was not installed.)
+ if test "x$IPTABLES_PATH" = x; then
+ IPTABLES_PATH='/usr/sbin/iptables'
fi
else
IPTABLES_PATH="$with_iptables"
@@ -957,7 +956,19 @@ fi
AC_DEFINE_UNQUOTED(IPTABLES_PATH, "$IPTABLES_PATH", [Define to path of iptables binary])
AC_SUBST(IPTABLES_PATH)
-# dnsmasq path
+AC_ARG_WITH(nft,
+ AS_HELP_STRING([--with-nft=/usr/sbin/nft], [path to nft]))
+if test "x${with_nft}" = x; then
+ AC_PATH_PROG(NFT_PATH, nft, [], $PATH:/sbin:/usr/sbin)
+ if test "x$NFT_PATH" = x; then
+ NFT_PATH='/usr/sbin/nft'
+ fi
+else
+ NFT_PATH="$with_nft"
+fi
+AC_DEFINE_UNQUOTED(NFT_PATH, "$NFT_PATH", [Define to path of nft binary])
+AC_SUBST(NFT_PATH)
+
AC_ARG_WITH(dnsmasq,
AS_HELP_STRING([--with-dnsmasq=/path/to/dnsmasq], [path to dnsmasq]))
if test "x${with_dnsmasq}" = x; then
@@ -1372,6 +1383,8 @@ echo " nmtui: $build_nmtui"
echo " nm-cloud-setup: $with_nm_cloud_setup"
echo " iwd: $ac_with_iwd"
echo " jansson: $have_jansson${JANSSON_SONAME:+ (soname: $JANSSON_SONAME)}"
+echo " iptables: $IPTABLES_PATH"
+echo " nft: $NFT_PATH"
echo
echo "Configuration plugins (main.plugins=${config_plugins_default})"
diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml
index 4320a9facd..d8fce34d6e 100644
--- a/man/NetworkManager.conf.xml
+++ b/man/NetworkManager.conf.xml
@@ -476,6 +476,24 @@ no-auto-default=*
</varlistentry>
<varlistentry>
+ <term><varname>firewall-backend</varname></term>
+ <listitem>
+ <para>
+ The firewall backend for configuring masquerading
+ with shared mode.
+ Set to either <literal>iptables</literal>, <literal>nftables</literal>
+ or <literal>none</literal>.
+ <literal>iptables</literal> and <literal>nftables</literal>
+ require <literal>iptables</literal> and <literal>nft</literal>
+ application, respectively.
+ <literal>none</literal> means to skip firewall configuration if
+ the users wish to manage firewall themselves.
+ If unspecified, it will be auto detected.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>iwd-config-path</varname></term>
<listitem>
<para>
diff --git a/meson.build b/meson.build
index 33e840c173..de50c652a7 100644
--- a/meson.build
+++ b/meson.build
@@ -691,7 +691,8 @@ dnssec_ts_paths = ['/usr/local/libexec',
'/usr/lib/dnssec-trigger']
# 0: cmdline option, 1: paths, 2: fallback
-progs = [['iptables', default_paths, '/sbin/iptables'],
+progs = [['iptables', default_paths, '/usr/sbin/iptables'],
+ ['nft', default_paths, '/usr/sbin/nft'],
['dnsmasq', default_paths, ''],
['dnssec_trigger', dnssec_ts_paths, join_paths(nm_libexecdir, 'dnssec-trigger-script') ],
]
@@ -1044,6 +1045,8 @@ if enable_ppp
endif
output += '\n'
output += ' jansson: ' + jansson_msg + '\n'
+output += ' iptables: ' + config_h.get('IPTABLES_PATH') + '\n'
+output += ' nft: ' + config_h.get('NFT_PATH') + '\n'
output += ' modemmanager-1: ' + enable_modem_manager.to_string() + '\n'
output += ' ofono: ' + enable_ofono.to_string() + '\n'
output += ' concheck: ' + enable_concheck.to_string() + '\n'
diff --git a/meson_options.txt b/meson_options.txt
index 5100ed71f5..14ed4077a0 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -5,6 +5,7 @@ option('udev_dir', type: 'string', value: '', description: 'Absolute path of the
option('dbus_conf_dir', type: 'string', value: '', description: 'where D-Bus system.d directory is')
option('kernel_firmware_dir', type: 'string', value: '/lib/firmware', description: 'where kernel firmware directory is (default is /lib/firmware)')
option('iptables', type: 'string', value: '', description: 'path to iptables')
+option('nft', type: 'string', value: '', description: 'path to nft')
option('dnsmasq', type: 'string', value: '', description: 'path to dnsmasq')
option('dnssec_trigger', type: 'string', value: '', description: 'path to unbound dnssec-trigger-script')
diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h
index 835edd5d7e..d1b2c3744a 100644
--- a/src/core/nm-config-data.h
+++ b/src/core/nm-config-data.h
@@ -50,71 +50,71 @@ typedef enum {
#define NM_CONFIG_DATA_NO_AUTO_DEFAULT "no-auto-default"
#define NM_CONFIG_DATA_DNS_MODE "dns"
-typedef enum { /*< flags >*/
- NM_CONFIG_GET_VALUE_NONE = 0,
+typedef enum {
+ NM_CONFIG_GET_VALUE_NONE = 0,
- /* use g_key_file_get_value() instead of g_key_file_get_string(). */
- NM_CONFIG_GET_VALUE_RAW = (1LL << 0),
+ /* use g_key_file_get_value() instead of g_key_file_get_string(). */
+ NM_CONFIG_GET_VALUE_RAW = (1LL << 0),
- /* strip whitespaces */
- NM_CONFIG_GET_VALUE_STRIP = (1LL << 1),
+ /* strip whitespaces */
+ NM_CONFIG_GET_VALUE_STRIP = (1LL << 1),
- /* if the returned string would be the empty word, return NULL. */
- NM_CONFIG_GET_VALUE_NO_EMPTY = (1LL << 2),
+ /* if the returned string would be the empty word, return NULL. */
+ NM_CONFIG_GET_VALUE_NO_EMPTY = (1LL << 2),
- /* special flag to read device spec. You want to use this before passing the
+ /* special flag to read device spec. You want to use this before passing the
* value to nm_match_spec_split(). */
- NM_CONFIG_GET_VALUE_TYPE_SPEC = NM_CONFIG_GET_VALUE_RAW,
+ NM_CONFIG_GET_VALUE_TYPE_SPEC = NM_CONFIG_GET_VALUE_RAW,
} NMConfigGetValueFlags;
-typedef enum { /*< flags >*/
- NM_CONFIG_CHANGE_NONE = 0,
+typedef enum {
+ NM_CONFIG_CHANGE_NONE = 0,
- /**************************************************************************
+ /**************************************************************************
* The external cause which triggered the reload/configuration-change
*************************************************************************/
- NM_CONFIG_CHANGE_CAUSE_SIGHUP = (1L << 0),
- NM_CONFIG_CHANGE_CAUSE_SIGUSR1 = (1L << 1),
- NM_CONFIG_CHANGE_CAUSE_SIGUSR2 = (1L << 2),
- NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT = (1L << 3),
- NM_CONFIG_CHANGE_CAUSE_SET_VALUES = (1L << 4),
- NM_CONFIG_CHANGE_CAUSE_CONF = (1L << 5),
- NM_CONFIG_CHANGE_CAUSE_DNS_RC = (1L << 6),
- NM_CONFIG_CHANGE_CAUSE_DNS_FULL = (1L << 7),
+ NM_CONFIG_CHANGE_CAUSE_SIGHUP = (1L << 0),
+ NM_CONFIG_CHANGE_CAUSE_SIGUSR1 = (1L << 1),
+ NM_CONFIG_CHANGE_CAUSE_SIGUSR2 = (1L << 2),
+ NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT = (1L << 3),
+ NM_CONFIG_CHANGE_CAUSE_SET_VALUES = (1L << 4),
+ NM_CONFIG_CHANGE_CAUSE_CONF = (1L << 5),
+ NM_CONFIG_CHANGE_CAUSE_DNS_RC = (1L << 6),
+ NM_CONFIG_CHANGE_CAUSE_DNS_FULL = (1L << 7),
- NM_CONFIG_CHANGE_CAUSES = ((1L << 8) - 1),
+ NM_CONFIG_CHANGE_CAUSES = ((1L << 8) - 1),
- /**************************************************************************
+ /**************************************************************************
* Following flags describe which property of the configuration changed:
*************************************************************************/
- /* main-file or config-description changed */
- NM_CONFIG_CHANGE_CONFIG_FILES = (1L << 10),
+ /* main-file or config-description changed */
+ NM_CONFIG_CHANGE_CONFIG_FILES = (1L << 10),
- /* any configuration on disk changed */
- NM_CONFIG_CHANGE_VALUES = (1L << 11),
+ /* any configuration on disk changed */
+ NM_CONFIG_CHANGE_VALUES = (1L << 11),
- /* any user configuration on disk changed (NetworkManager.conf) */
- NM_CONFIG_CHANGE_VALUES_USER = (1L << 12),
+ /* any user configuration on disk changed (NetworkManager.conf) */
+ NM_CONFIG_CHANGE_VALUES_USER = (1L << 12),
- /* any internal configuration on disk changed (NetworkManager-intern.conf) */
- NM_CONFIG_CHANGE_VALUES_INTERN = (1L << 13),
+ /* any internal configuration on disk changed (NetworkManager-intern.conf) */
+ NM_CONFIG_CHANGE_VALUES_INTERN = (1L << 13),
- /* configuration regarding connectivity changed */
- NM_CONFIG_CHANGE_CONNECTIVITY = (1L << 14),
+ /* configuration regarding connectivity changed */
+ NM_CONFIG_CHANGE_CONNECTIVITY = (1L << 14),
- /* configuration regarding no-auto-default changed */
- NM_CONFIG_CHANGE_NO_AUTO_DEFAULT = (1L << 15),
+ /* configuration regarding no-auto-default changed */
+ NM_CONFIG_CHANGE_NO_AUTO_DEFAULT = (1L << 15),
- /* configuration regarding dns-mode changed */
- NM_CONFIG_CHANGE_DNS_MODE = (1L << 16),
+ /* configuration regarding dns-mode changed */
+ NM_CONFIG_CHANGE_DNS_MODE = (1L << 16),
- /* configuration regarding rc-manager changed */
- NM_CONFIG_CHANGE_RC_MANAGER = (1L << 17),
+ /* configuration regarding rc-manager changed */
+ NM_CONFIG_CHANGE_RC_MANAGER = (1L << 17),
- /* configuration regarding global dns-config changed */
- NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG = (1L << 18),
+ /* configuration regarding global dns-config changed */
+ NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG = (1L << 18),
} NMConfigChangeFlags;
diff --git a/src/core/nm-firewall-utils.c b/src/core/nm-firewall-utils.c
index dc17e2c040..cc342b3f22 100644
--- a/src/core/nm-firewall-utils.c
+++ b/src/core/nm-firewall-utils.c
@@ -9,8 +9,34 @@
#include "nm-firewall-utils.h"
#include "libnm-glib-aux/nm-str-buf.h"
+#include "libnm-glib-aux/nm-io-utils.h"
#include "libnm-platform/nm-platform.h"
+#include "nm-config.h"
+#include "NetworkManagerUtils.h"
+
+/*****************************************************************************/
+
+static const struct {
+ const char *name;
+ const char *path;
+} FirewallBackends[] = {
+ [NM_FIREWALL_BACKEND_NONE - 1] =
+ {
+ .name = "none",
+ },
+ [NM_FIREWALL_BACKEND_NFTABLES - 1] =
+ {
+ .name = "nftables",
+ .path = NFT_PATH,
+ },
+ [NM_FIREWALL_BACKEND_IPTABLES - 1] =
+ {
+ .name = "iptables",
+ .path = IPTABLES_PATH,
+ },
+};
+
/*****************************************************************************/
#define _SHARE_IPTABLES_SUBNET_TO_STR_LEN (INET_ADDRSTRLEN + 1 + 2 + 1)
@@ -36,7 +62,7 @@ _share_iptables_subnet_to_str(char buf[static _SHARE_IPTABLES_SUBNET_TO_STR
}
static char *
-_share_iptables_get_name(gboolean is_comment, const char *prefix, const char *ip_iface)
+_share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const char *ip_iface)
{
NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, FALSE);
gsize ip_iface_len;
@@ -58,7 +84,7 @@ _share_iptables_get_name(gboolean is_comment, const char *prefix, const char *ip
* an plain name.
*
* That means, for chain names the prefix must be at most 8 chars long. */
- nm_assert(is_comment || (strlen(prefix) <= 8));
+ nm_assert(!is_iptables_chain || (strlen(prefix) <= 8));
nm_str_buf_append(&strbuf, prefix);
@@ -150,7 +176,7 @@ _share_iptables_set_masquerade(gboolean add, const char *ip_iface, in_addr_t add
char str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
gs_free char *comment_name = NULL;
- comment_name = _share_iptables_get_name(TRUE, "nm-shared", ip_iface);
+ comment_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
_share_iptables_subnet_to_str(str_subnet, addr, plen);
_share_iptables_call("" IPTABLES_PATH "",
@@ -290,9 +316,9 @@ _share_iptables_set_shared(gboolean add, const char *ip_iface, in_addr_t addr, g
gs_free char *chain_input = NULL;
gs_free char *chain_forward = NULL;
- comment_name = _share_iptables_get_name(TRUE, "nm-shared", ip_iface);
- chain_input = _share_iptables_get_name(FALSE, "nm-sh-in", ip_iface);
- chain_forward = _share_iptables_get_name(FALSE, "nm-sh-fw", ip_iface);
+ comment_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
+ chain_input = _share_iptables_get_name(TRUE, "nm-sh-in", ip_iface);
+ chain_forward = _share_iptables_get_name(TRUE, "nm-sh-fw", ip_iface);
if (add)
_share_iptables_set_shared_chains_add(chain_input, chain_forward, ip_iface, addr, plen);
@@ -327,6 +353,333 @@ _share_iptables_set_shared(gboolean add, const char *ip_iface, in_addr_t addr, g
_share_iptables_set_shared_chains_delete(chain_input, chain_forward);
}
+/*****************************************************************************/
+
+typedef struct {
+ GTask * task;
+ GSubprocess * subprocess;
+ GSource * timeout_source;
+ GCancellable *intern_cancellable;
+ char * identifier;
+ gulong cancellable_id;
+} FwNftCallData;
+
+static void
+_fw_nft_call_data_free(FwNftCallData *call_data, GError *error_take)
+{
+ nm_clear_g_signal_handler(g_task_get_cancellable(call_data->task), &call_data->cancellable_id);
+ nm_clear_g_cancellable(&call_data->intern_cancellable);
+ nm_clear_g_source_inst(&call_data->timeout_source);
+
+ if (error_take)
+ g_task_return_error(call_data->task, g_steal_pointer(&error_take));
+ else
+ g_task_return_boolean(call_data->task, TRUE);
+
+ g_object_unref(call_data->task);
+ nm_g_object_unref(call_data->subprocess);
+ g_free(call_data->identifier);
+
+ nm_g_slice_free(call_data);
+}
+
+static void
+_fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ FwNftCallData *call_data = user_data;
+ gs_free_error GError *error = NULL;
+ gs_unref_bytes GBytes *stdout_buf = NULL;
+ gs_unref_bytes GBytes *stderr_buf = NULL;
+
+ nm_assert(source == (gpointer) call_data->subprocess);
+
+ if (!g_subprocess_communicate_finish(G_SUBPROCESS(source),
+ result,
+ &stdout_buf,
+ &stderr_buf,
+ &error)) {
+ /* on any error, the process might still be running. We need to abort it in
+ * the background... */
+ if (nm_utils_error_is_cancelled(error)) {
+ nm_log_dbg(LOGD_SHARING,
+ "firewall: ntf[%s]: communication cancelled. Kill process",
+ call_data->identifier);
+ } else {
+ nm_log_dbg(LOGD_SHARING,
+ "firewall: nft[%s]: communication failed: %s. Kill process",
+ call_data->identifier,
+ error->message);
+ }
+
+ {
+ _nm_unused nm_auto_pop_gmaincontext GMainContext *main_context =
+ nm_g_main_context_push_thread_default(NULL);
+
+ nm_shutdown_wait_obj_register_object(call_data->subprocess, "nft-terminate");
+ G_STATIC_ASSERT_EXPR(200 < NM_SHUTDOWN_TIMEOUT_MS_WATCHDOG * 2 / 3);
+ nm_g_subprocess_terminate_in_background(call_data->subprocess, 200);
+ }
+ } else if (g_subprocess_get_successful(call_data->subprocess)) {
+ nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: command successful", call_data->identifier);
+ } else {
+ gs_free char *ss_stdout = NULL;
+ gs_free char *ss_stderr = NULL;
+ gboolean print_stdout = (stdout_buf && g_bytes_get_size(stdout_buf) > 0);
+ gboolean print_stderr = (stderr_buf && g_bytes_get_size(stderr_buf) > 0);
+
+ nm_log_warn(LOGD_SHARING,
+ "firewall: nft[%s]: command failed:%s%s%s%s%s%s%s",
+ call_data->identifier,
+ print_stdout || print_stderr ? "" : " unknown reason",
+ NM_PRINT_FMT_QUOTED(
+ print_stdout,
+ " (stdout: \"",
+ nm_utils_buf_utf8safe_escape_bytes(stdout_buf,
+ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+ &ss_stdout),
+ "\")",
+ ""),
+ NM_PRINT_FMT_QUOTED(
+ print_stderr,
+ " (stderr: \"",
+ nm_utils_buf_utf8safe_escape_bytes(stderr_buf,
+ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+ &ss_stderr),
+ "\")",
+ ""));
+ }
+
+ _fw_nft_call_data_free(call_data, error);
+}
+
+static void
+_fw_nft_call_cancelled_cb(GCancellable *cancellable, gpointer user_data)
+{
+ FwNftCallData *call_data = user_data;
+
+ if (call_data->cancellable_id == 0)
+ return;
+
+ nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: operation cancelled", call_data->identifier);
+
+ nm_clear_g_signal_handler(g_task_get_cancellable(call_data->task), &call_data->cancellable_id);
+ nm_clear_g_cancellable(&call_data->intern_cancellable);
+}
+
+static gboolean
+_fw_nft_call_timeout_cb(gpointer user_data)
+{
+ FwNftCallData *call_data = user_data;
+
+ nm_clear_g_source_inst(&call_data->timeout_source);
+ nm_log_dbg(LOGD_SHARING,
+ "firewall: nft[%s]: cancel operation after timeout",
+ call_data->identifier);
+
+ nm_clear_g_cancellable(&call_data->intern_cancellable);
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+_fw_nft_call(GBytes * stdin_buf,
+ GCancellable * cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_user_data)
+{
+ gs_unref_object GSubprocessLauncher *subprocess_launcher = NULL;
+ gs_free_error GError *error = NULL;
+ FwNftCallData * call_data;
+
+ call_data = g_slice_new(FwNftCallData);
+ *call_data = (FwNftCallData){
+ .task = nm_g_task_new(NULL, cancellable, _fw_nft_call, callback, callback_user_data),
+ .subprocess = NULL,
+ .timeout_source = NULL,
+ };
+
+ if (cancellable) {
+ call_data->cancellable_id = g_cancellable_connect(cancellable,
+ G_CALLBACK(_fw_nft_call_cancelled_cb),
+ call_data,
+ NULL);
+ if (call_data->cancellable_id == 0) {
+ nm_log_dbg(LOGD_SHARING, "firewall: nft: already cancelled");
+ nm_utils_error_set_cancelled(&error, FALSE, NULL);
+ _fw_nft_call_data_free(call_data, g_steal_pointer(&error));
+ return;
+ }
+ }
+
+ subprocess_launcher =
+ g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE
+ | G_SUBPROCESS_FLAGS_STDERR_PIPE);
+ g_subprocess_launcher_set_environ(subprocess_launcher, NM_STRV_EMPTY());
+
+ call_data->subprocess = g_subprocess_launcher_spawnv(subprocess_launcher,
+ NM_MAKE_STRV(NFT_PATH, "-f", "-"),
+ &error);
+
+ if (!call_data->subprocess) {
+ nm_log_dbg(LOGD_SHARING, "firewall: nft: spawning nft failed: %s", error->message);
+ _fw_nft_call_data_free(call_data, g_steal_pointer(&error));
+ return;
+ }
+
+ call_data->identifier = g_strdup(g_subprocess_get_identifier(call_data->subprocess));
+
+ nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: communicate with nft", call_data->identifier);
+
+ nm_shutdown_wait_obj_register_object(call_data->task, "nft-call");
+
+ call_data->intern_cancellable = g_cancellable_new(),
+
+ g_subprocess_communicate_async(call_data->subprocess,
+ stdin_buf,
+ call_data->intern_cancellable,
+ _fw_nft_call_communicate_cb,
+ call_data);
+
+ call_data->timeout_source =
+ nm_g_source_attach(nm_g_timeout_source_new((NM_SHUTDOWN_TIMEOUT_MS * 2) / 3,
+ G_PRIORITY_DEFAULT,
+ _fw_nft_call_timeout_cb,
+ call_data,
+ NULL),
+ g_task_get_context(call_data->task));
+}
+
+static gboolean
+_fw_nft_call_finish(GAsyncResult *result, GError **error)
+{
+ g_return_val_if_fail(nm_g_task_is_valid(result, NULL, _fw_nft_call), FALSE);
+
+ return g_task_propagate_boolean(G_TASK(result), error);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ GMainLoop *loop;
+ GError ** error;
+ gboolean success;
+} FwNftCallSyncData;
+
+static void
+_fw_nft_call_sync_done(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ FwNftCallSyncData *data = user_data;
+
+ data->success = _fw_nft_call_finish(result, data->error);
+ g_main_loop_quit(data->loop);
+}
+
+static gboolean
+_fw_nft_call_sync(GBytes *stdin_buf, GError **error)
+{
+ nm_auto_pop_and_unref_gmaincontext GMainContext *main_context =
+ nm_g_main_context_push_thread_default(g_main_context_new());
+ nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(main_context, FALSE);
+ FwNftCallSyncData data = (FwNftCallSyncData){
+ .loop = main_loop,
+ .error = error,
+ };
+
+ _fw_nft_call(stdin_buf, NULL, _fw_nft_call_sync_done, &data);
+
+ g_main_loop_run(main_loop);
+ return data.success;
+}
+
+/*****************************************************************************/
+
+static void
+_fw_nft_set(gboolean add, const char *ip_iface, in_addr_t addr, guint8 plen)
+{
+ nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
+ gs_unref_bytes GBytes *stdin_buf = NULL;
+ gs_free char * table_name = NULL;
+ gs_free char * ss1 = NULL;
+ char str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
+
+ table_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
+
+ _share_iptables_subnet_to_str(str_subnet, addr, plen);
+
+#define _append(p_strbuf, fmt, ...) nm_str_buf_append_printf((p_strbuf), "" fmt "\n", ##__VA_ARGS__)
+
+ _append(&strbuf, "add table inet %s", table_name);
+ _append(&strbuf, "%s table inet %s", add ? "flush" : "delete", table_name);
+
+ if (add) {
+ _append(&strbuf,
+ "add chain inet %s nat_postrouting {"
+ " type nat hook postrouting priority 100; policy accept; "
+ "};",
+ table_name);
+ _append(&strbuf,
+ "add rule inet %s nat_postrouting ip saddr %s ip daddr != %s masquerade;",
+ table_name,
+ str_subnet,
+ str_subnet);
+
+ /* This filter_input chain serves no real purpose, because "accept" only stops
+ * evaluation of the current rule. It cannot fully accept the packet. Since
+ * this chain has no other rules, it is useless in this form.
+ */
+ /*
+ _append(&strbuf,
+ "add chain inet %s filter_input {"
+ " type filter hook input priority 0; policy accept; "
+ "};",
+ table_name);
+ _append(&strbuf, "add rule inet %s filter_input tcp dport { 67, 53 } accept;", table_name);
+ _append(&strbuf, "add rule inet %s filter_input udp dport { 67, 53 } accept;", table_name);
+ */
+
+ _append(&strbuf,
+ "add chain inet %s filter_forward {"
+ " type filter hook forward priority 0; policy accept; "
+ "};",
+ table_name);
+ _append(&strbuf,
+ "add rule inet %s filter_forward ip daddr %s oifname \"%s\" "
+ " ct state { established, related } accept;",
+ table_name,
+ str_subnet,
+ ip_iface);
+ _append(&strbuf,
+ "add rule inet %s filter_forward ip saddr %s iifname \"%s\" accept;",
+ table_name,
+ str_subnet,
+ ip_iface);
+ _append(&strbuf,
+ "add rule inet %s filter_forward iifname \"%s\" oifname \"%s\" accept;",
+ table_name,
+ ip_iface,
+ ip_iface);
+ _append(&strbuf,
+ "add rule inet %s filter_forward iifname \"%s\" reject;",
+ table_name,
+ ip_iface);
+ _append(&strbuf,
+ "add rule inet %s filter_forward oifname \"%s\" reject;",
+ table_name,
+ ip_iface);
+ }
+
+ nm_log_trace(LOGD_SHARING,
+ "firewall: nft command: [ %s ]",
+ nm_utils_str_utf8safe_escape(nm_str_buf_get_str(&strbuf),
+ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+ &ss1));
+
+ stdin_buf = g_bytes_new_static(nm_str_buf_get_str(&strbuf), strbuf.len);
+
+ _fw_nft_call_sync(stdin_buf, NULL);
+}
+
+/*****************************************************************************/
+
struct _NMFirewallConfig {
char * ip_iface;
in_addr_t addr;
@@ -364,6 +717,94 @@ nm_firewall_config_free(NMFirewallConfig *self)
void
nm_firewall_config_apply(NMFirewallConfig *self, gboolean shared)
{
- _share_iptables_set_masquerade(shared, self->ip_iface, self->addr, self->plen);
- _share_iptables_set_shared(shared, self->ip_iface, self->addr, self->plen);
+ switch (nm_firewall_utils_get_backend()) {
+ case NM_FIREWALL_BACKEND_IPTABLES:
+ _share_iptables_set_masquerade(shared, self->ip_iface, self->addr, self->plen);
+ _share_iptables_set_shared(shared, self->ip_iface, self->addr, self->plen);
+ break;
+ case NM_FIREWALL_BACKEND_NFTABLES:
+ _fw_nft_set(shared, self->ip_iface, self->addr, self->plen);
+ break;
+ case NM_FIREWALL_BACKEND_NONE:
+ break;
+ default:
+ nm_assert_not_reached();
+ break;
+ }
+}
+
+/*****************************************************************************/
+
+static NMFirewallBackend
+_firewall_backend_detect(void)
+{
+ if (g_file_test(NFT_PATH, G_FILE_TEST_IS_EXECUTABLE))
+ return NM_FIREWALL_BACKEND_NFTABLES;
+ if (g_file_test(IPTABLES_PATH, G_FILE_TEST_IS_EXECUTABLE))
+ return NM_FIREWALL_BACKEND_IPTABLES;
+
+ return NM_FIREWALL_BACKEND_NFTABLES;
+}
+
+NMFirewallBackend
+nm_firewall_utils_get_backend(void)
+{
+ static int backend = NM_FIREWALL_BACKEND_UNKNOWN;
+ int b;
+
+again:
+ b = g_atomic_int_get(&backend);
+ if (b == NM_FIREWALL_BACKEND_UNKNOWN) {
+ gs_free char *conf_value = NULL;
+ gboolean detect;
+ int i;
+
+ conf_value =
+ nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG,
+ NM_CONFIG_KEYFILE_GROUP_MAIN,
+ NM_CONFIG_KEYFILE_KEY_MAIN_FIREWALL_BACKEND,
+ NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
+
+ if (conf_value) {
+ for (i = 0; i < (int) G_N_ELEMENTS(FirewallBackends); i++) {
+ if (!g_ascii_strcasecmp(conf_value, FirewallBackends[i].name)) {
+ b = (i + 1);
+ break;
+ }
+ }
+ }
+
+ detect = (b == NM_FIREWALL_BACKEND_UNKNOWN);
+ if (detect)
+ b = _firewall_backend_detect();
+
+ nm_assert(NM_IN_SET(b,
+ NM_FIREWALL_BACKEND_NONE,
+ NM_FIREWALL_BACKEND_IPTABLES,
+ NM_FIREWALL_BACKEND_NFTABLES));
+
+ if (!g_atomic_int_compare_and_exchange(&backend, NM_FIREWALL_BACKEND_UNKNOWN, b))
+ goto again;
+
+ nm_log_dbg(LOGD_SHARING,
+ "firewall: use %s backend%s%s%s%s%s%s%s",
+ FirewallBackends[b - 1].name,
+ NM_PRINT_FMT_QUOTED(FirewallBackends[b - 1].path,
+ " (",
+ FirewallBackends[b - 1].path,
+ ")",
+ ""),
+ detect ? " (detected)" : "",
+ NM_PRINT_FMT_QUOTED(detect && conf_value,
+ " (invalid setting \"",
+ conf_value,
+ "\")",
+ ""));
+ }
+
+ nm_assert(NM_IN_SET(b,
+ NM_FIREWALL_BACKEND_NONE,
+ NM_FIREWALL_BACKEND_IPTABLES,
+ NM_FIREWALL_BACKEND_NFTABLES));
+ return b;
}
diff --git a/src/core/nm-firewall-utils.h b/src/core/nm-firewall-utils.h
index 5ba1721c2e..3d6c8a6962 100644
--- a/src/core/nm-firewall-utils.h
+++ b/src/core/nm-firewall-utils.h
@@ -7,6 +7,17 @@
#ifndef __NM_FIREWALL_UTILS_H__
#define __NM_FIREWALL_UTILS_H__
+typedef enum {
+ NM_FIREWALL_BACKEND_UNKNOWN,
+ NM_FIREWALL_BACKEND_NONE,
+ NM_FIREWALL_BACKEND_IPTABLES,
+ NM_FIREWALL_BACKEND_NFTABLES,
+} NMFirewallBackend;
+
+NMFirewallBackend nm_firewall_utils_get_backend(void);
+
+/*****************************************************************************/
+
typedef struct _NMFirewallConfig NMFirewallConfig;
NMFirewallConfig *nm_firewall_config_new(const char *ip_iface, in_addr_t addr, guint8 plen);
diff --git a/src/core/tests/test-core.c b/src/core/tests/test-core.c
index d7f733066a..15be7af1ed 100644
--- a/src/core/tests/test-core.c
+++ b/src/core/tests/test-core.c
@@ -22,6 +22,25 @@
#include "nm-test-utils-core.h"
+/*****************************************************************************/
+
+static void
+test_config_h(void)
+{
+#define ABSOLUTE_PATH(path) \
+ G_STMT_START \
+ { \
+ g_assert_cmpstr("" path "", !=, ""); \
+ g_assert("" path ""[0] == '/'); \
+ } \
+ G_STMT_END
+
+ ABSOLUTE_PATH(IPTABLES_PATH);
+ ABSOLUTE_PATH(NFT_PATH);
+}
+
+/*****************************************************************************/
+
/* Reference implementation for nm_utils_ip6_address_clear_host_address.
* Taken originally from set_address_masked(), src/ndisc/nm-lndp-ndisc.c
**/
@@ -2570,6 +2589,8 @@ main(int argc, char **argv)
{
nmtst_init_with_logging(&argc, &argv, NULL, "ALL");
+ g_test_add_func("/general/test_config_h", test_config_h);
+
g_test_add_func("/general/test_logging_domains", test_logging_domains);
g_test_add_func("/general/test_logging_error", test_logging_error);
diff --git a/src/libnm-base/nm-config-base.h b/src/libnm-base/nm-config-base.h
index 6a2bab1548..7a23875a43 100644
--- a/src/libnm-base/nm-config-base.h
+++ b/src/libnm-base/nm-config-base.h
@@ -26,15 +26,16 @@
#define NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG "debug"
#define NM_CONFIG_KEYFILE_KEY_MAIN_DHCP "dhcp"
#define NM_CONFIG_KEYFILE_KEY_MAIN_DNS "dns"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_FIREWALL_BACKEND "firewall-backend"
#define NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE "hostname-mode"
#define NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER "ignore-carrier"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH "iwd-config-path"
#define NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES "monitor-connection-files"
#define NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT "no-auto-default"
#define NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS "plugins"
#define NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER "rc-manager"
#define NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER "slaves-order"
#define NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED "systemd-resolved"
-#define NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH "iwd-config-path"
#define NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT "audit"
#define NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND "backend"
diff --git a/src/libnm-glib-aux/nm-io-utils.c b/src/libnm-glib-aux/nm-io-utils.c
index 0176abf7f8..894c8726c0 100644
--- a/src/libnm-glib-aux/nm-io-utils.c
+++ b/src/libnm-glib-aux/nm-io-utils.c
@@ -491,3 +491,76 @@ nm_utils_fd_read(int fd, NMStrBuf *out_string)
return n_read;
}
+
+/*****************************************************************************/
+
+typedef struct {
+ GSubprocess *subprocess;
+ GSource * timeout_source;
+} SubprocessTerminateData;
+
+static void
+_subprocess_terminate_wait_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ SubprocessTerminateData *term_data = user_data;
+
+ g_subprocess_wait_finish(G_SUBPROCESS(source), result, NULL);
+
+ nm_clear_g_source_inst(&term_data->timeout_source);
+ g_object_unref(term_data->subprocess);
+ nm_g_slice_free(term_data);
+}
+
+static gboolean
+_subprocess_terminate_timeout_cb(gpointer user_data)
+{
+ SubprocessTerminateData *term_data = user_data;
+
+ nm_clear_g_source_inst(&term_data->timeout_source);
+ g_subprocess_send_signal(term_data->subprocess, SIGKILL);
+ return G_SOURCE_REMOVE;
+}
+
+void
+nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeout_msec_before_kill)
+{
+ SubprocessTerminateData *term_data;
+ GMainContext * main_context;
+
+ nm_assert(timeout_msec_before_kill > 0);
+
+ /* The GSubprocess stays alive until the child is reaped (an internal reference is held).
+ *
+ * This function first sends SIGTERM to the process right away, and after a
+ * timeout "timeout_msec_before_kill" send a SIGKILL.
+ *
+ * Otherwise, it does nothing, it does not log, there is no notification when the process
+ * completes and there is no way to abort the thing.
+ *
+ * It honors the current g_main_context_get_thread_default(). */
+
+ if (!subprocess)
+ return;
+
+ g_return_if_fail(G_IS_SUBPROCESS(subprocess));
+
+ main_context = g_main_context_get_thread_default();
+
+ term_data = g_slice_new(SubprocessTerminateData);
+ *term_data = (SubprocessTerminateData){
+ .subprocess = g_object_ref(subprocess),
+ .timeout_source = NULL,
+ };
+
+ g_subprocess_send_signal(subprocess, SIGTERM);
+
+ g_subprocess_wait_async(subprocess, NULL, _subprocess_terminate_wait_cb, term_data);
+
+ term_data->timeout_source =
+ nm_g_source_attach(nm_g_timeout_source_new(timeout_msec_before_kill,
+ G_PRIORITY_DEFAULT,
+ _subprocess_terminate_timeout_cb,
+ term_data,
+ NULL),
+ main_context);
+}
diff --git a/src/libnm-glib-aux/nm-io-utils.h b/src/libnm-glib-aux/nm-io-utils.h
index 2b132ff07d..98e63ac01e 100644
--- a/src/libnm-glib-aux/nm-io-utils.h
+++ b/src/libnm-glib-aux/nm-io-utils.h
@@ -56,4 +56,6 @@ struct stat;
int nm_utils_file_stat(const char *filename, struct stat *out_st);
+void nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeout_msec_before_kill);
+
#endif /* __NM_IO_UTILS_H__ */
diff --git a/src/libnm-glib-aux/nm-macros-internal.h b/src/libnm-glib-aux/nm-macros-internal.h
index a6910b2d3b..edaa6f3902 100644
--- a/src/libnm-glib-aux/nm-macros-internal.h
+++ b/src/libnm-glib-aux/nm-macros-internal.h
@@ -1082,6 +1082,13 @@ nm_clear_error(GError **err)
}
}
+static inline void
+nm_g_error_free(GError *err)
+{
+ if (err)
+ g_error_free(err);
+}
+
/* Patch g_clear_error() to use nm_clear_error(), which is inlineable
* and visible to the compiler. For example gs_free_error attribute only
* frees the error after checking that it's not %NULL. So, in many cases
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index 699d4d52b2..7bd3845a56 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -522,6 +522,7 @@ gboolean nm_utils_memeqzero(gconstpointer data, gsize length);
extern const void *const _NM_PTRARRAY_EMPTY[1];
#define NM_PTRARRAY_EMPTY(type) ((type const *) _NM_PTRARRAY_EMPTY)
+#define NM_STRV_EMPTY() ((char **) _NM_PTRARRAY_EMPTY)
static inline void
_nm_utils_strbuf_init(char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len)
@@ -1563,6 +1564,18 @@ NM_AUTO_DEFINE_FCN0(GSource *, _nm_auto_destroy_and_unref_gsource, nm_g_source_d
NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_pop_gmaincontext, g_main_context_pop_thread_default);
#define nm_auto_pop_gmaincontext nm_auto(_nm_auto_pop_gmaincontext)
+static inline void
+nm_g_main_context_pop_and_unref(GMainContext *context)
+{
+ g_main_context_pop_thread_default(context);
+ g_main_context_unref(context);
+}
+
+NM_AUTO_DEFINE_FCN0(GMainContext *,
+ _nm_auto_pop_and_unref_gmaincontext,
+ nm_g_main_context_pop_and_unref);
+#define nm_auto_pop_and_unref_gmaincontext nm_auto(_nm_auto_pop_and_unref_gmaincontext)
+
static inline gboolean
nm_source_func_unref_gobject(gpointer user_data)
{