diff options
author | Thomas Haller <thaller@redhat.com> | 2021-05-14 11:41:43 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2021-05-14 11:41:43 +0200 |
commit | 6f04f5bc2fe242fc24d476f32507cc380ae57934 (patch) | |
tree | a732ee942ab87cebf9ae3ad2dd8c5b174e2231a1 | |
parent | 63bb7580bc9fd4e0391917c7aff09a7ed0e55cd7 (diff) | |
parent | a79d5e2218d4adc40cb8794b56845b06cb5a2478 (diff) | |
download | NetworkManager-6f04f5bc2fe242fc24d476f32507cc380ae57934.tar.gz |
firewall: merge branch 'th/firewall-nft'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/847
-rw-r--r-- | config.h.meson | 5 | ||||
-rw-r--r-- | configure.ac | 23 | ||||
-rw-r--r-- | man/NetworkManager.conf.xml | 18 | ||||
-rw-r--r-- | meson.build | 5 | ||||
-rw-r--r-- | meson_options.txt | 1 | ||||
-rw-r--r-- | src/core/nm-config-data.h | 82 | ||||
-rw-r--r-- | src/core/nm-firewall-utils.c | 457 | ||||
-rw-r--r-- | src/core/nm-firewall-utils.h | 11 | ||||
-rw-r--r-- | src/core/tests/test-core.c | 21 | ||||
-rw-r--r-- | src/libnm-base/nm-config-base.h | 3 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-io-utils.c | 73 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-io-utils.h | 2 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-macros-internal.h | 7 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.h | 13 |
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) { |