mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-02 08:50:12 +01:00
firewall: merge branch 'th/firewall-nft'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/847
This commit is contained in:
commit
6f04f5bc2f
14 changed files with 664 additions and 57 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
23
configure.ac
23
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})"
|
||||
|
|
|
|||
|
|
@ -475,6 +475,24 @@ no-auto-default=*
|
|||
</listitem>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue