mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-03-01 11:30:31 +01:00
Background
==========
Imagine you run a container on your machine. Then the routing table
might look like:
default via 10.0.10.1 dev eth0 proto dhcp metric 100
10.0.10.0/28 dev eth0 proto kernel scope link src 10.0.10.5 metric 100
[...]
10.42.0.0/24 via 10.42.0.0 dev flannel.1 onlink
10.42.1.2 dev cali02ad7e68ce1 scope link
10.42.1.3 dev cali8fcecf5aaff scope link
10.42.2.0/24 via 10.42.2.0 dev flannel.1 onlink
10.42.3.0/24 via 10.42.3.0 dev flannel.1 onlink
That is, there are another interfaces with subnets and specific routes.
If nm-cloud-setup now configures rules:
0: from all lookup local
30400: from 10.0.10.5 lookup 30400
32766: from all lookup main
32767: from all lookup default
and
default via 10.0.10.1 dev eth0 table 30400 proto static metric 10
10.0.10.1 dev eth0 table 30400 proto static scope link metric 10
then these other subnets will also be reached via the default route.
This container example is just one case where this is a problem. In
general, if you have specific routes on another interface, then the
default route in the 30400+ table will interfere badly.
The idea of nm-cloud-setup is to automatically configure the network for
secondary IP addresses. When the user has special requirements, then
they should disable nm-cloud-setup and configure whatever they want.
But the container use case is popular and important. It is not something
where the user actively configures the network. This case needs to work better,
out of the box. In general, nm-cloud-setup should work better with the
existing network configuration.
Change
======
Add new routing tables 30200+ with the individual subnets of the
interface:
10.0.10.0/24 dev eth0 table 30200 proto static metric 10
[...]
default via 10.0.10.1 dev eth0 table 30400 proto static metric 10
10.0.10.1 dev eth0 table 30400 proto static scope link metric 10
Also add more important routing rules with priority 30200+, which select
these tables based on the source address:
30200: from 10.0.10.5 lookup 30200
These will do source based routing for the subnets on these
interfaces.
Then, add a rule with priority 30350
30350: lookup main suppress_prefixlength 0
which processes the routes from the main table, but ignores the default
routes. 30350 was chosen, because it's in between the rules 30200+ and
30400+, leaving a range for the user to configure their own rules.
Then, as before, the rules 30400+ again look at the corresponding 30400+
table, to find a default route.
Finally, process the main table again, this time honoring the default
route. That is for packets that have a different source address.
This change means that the source based routing is used for the
subnets that are configured on the interface and for the default route.
Whereas, if there are any more specific routes in the main table, they will
be preferred over the default route.
Apparently Amazon Linux solves this differently, by not configuring a
routing table for addresses on interface "eth0". That might be an
alternative, but it's not clear to me what is special about eth0 to
warrant this treatment. It also would imply that we somehow recognize
this primary interface. In practise that would be doable by selecting
the interface with "iface_idx" zero.
Instead choose this approach. This is remotely similar to what WireGuard does
for configuring the default route ([1]), however WireGuard uses fwmark to match
the packets instead of the source address.
[1] https://www.wireguard.com/netns/#improved-rule-based-routing
(cherry picked from commit fe80b2d1ec)
(cherry picked from commit 58e58361bd)
681 lines
24 KiB
C
681 lines
24 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "libnm/nm-default-client.h"
|
|
|
|
#include "nm-libnm-aux/nm-libnm-aux.h"
|
|
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include "nm-cloud-setup-utils.h"
|
|
#include "nmcs-provider-ec2.h"
|
|
#include "nmcs-provider-gcp.h"
|
|
#include "nmcs-provider-azure.h"
|
|
#include "nm-libnm-core-intern/nm-libnm-core-utils.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
GMainLoop * main_loop;
|
|
GCancellable *cancellable;
|
|
NMCSProvider *provider_result;
|
|
guint detect_count;
|
|
} ProviderDetectData;
|
|
|
|
static void
|
|
_provider_detect_cb(GObject *source, GAsyncResult *result, gpointer user_data)
|
|
{
|
|
gs_unref_object NMCSProvider *provider = NMCS_PROVIDER(source);
|
|
gs_free_error GError *error = NULL;
|
|
ProviderDetectData * dd;
|
|
gboolean success;
|
|
|
|
success = nmcs_provider_detect_finish(provider, result, &error);
|
|
|
|
nm_assert(success != (!!error));
|
|
|
|
if (nm_utils_error_is_cancelled(error))
|
|
return;
|
|
|
|
dd = user_data;
|
|
|
|
nm_assert(dd->detect_count > 0);
|
|
dd->detect_count--;
|
|
|
|
if (error) {
|
|
_LOGI("provider %s not detected: %s", nmcs_provider_get_name(provider), error->message);
|
|
if (dd->detect_count > 0) {
|
|
/* wait longer. */
|
|
return;
|
|
}
|
|
|
|
_LOGI("no provider detected");
|
|
goto done;
|
|
}
|
|
|
|
_LOGI("provider %s detected", nmcs_provider_get_name(provider));
|
|
dd->provider_result = g_steal_pointer(&provider);
|
|
|
|
done:
|
|
g_cancellable_cancel(dd->cancellable);
|
|
g_main_loop_quit(dd->main_loop);
|
|
}
|
|
|
|
static void
|
|
_provider_detect_sigterm_cb(GCancellable *source, gpointer user_data)
|
|
{
|
|
ProviderDetectData *dd = user_data;
|
|
|
|
g_cancellable_cancel(dd->cancellable);
|
|
g_clear_object(&dd->provider_result);
|
|
dd->detect_count = 0;
|
|
g_main_loop_quit(dd->main_loop);
|
|
}
|
|
|
|
static NMCSProvider *
|
|
_provider_detect(GCancellable *sigterm_cancellable)
|
|
{
|
|
nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);
|
|
gs_unref_object GCancellable *cancellable = g_cancellable_new();
|
|
gs_unref_object NMHttpClient *http_client = NULL;
|
|
ProviderDetectData dd = {
|
|
.cancellable = cancellable,
|
|
.main_loop = main_loop,
|
|
.detect_count = 0,
|
|
.provider_result = NULL,
|
|
};
|
|
const GType gtypes[] = {
|
|
NMCS_TYPE_PROVIDER_EC2,
|
|
NMCS_TYPE_PROVIDER_GCP,
|
|
NMCS_TYPE_PROVIDER_AZURE,
|
|
};
|
|
int i;
|
|
gulong cancellable_signal_id;
|
|
|
|
cancellable_signal_id = g_cancellable_connect(sigterm_cancellable,
|
|
G_CALLBACK(_provider_detect_sigterm_cb),
|
|
&dd,
|
|
NULL);
|
|
if (!cancellable_signal_id)
|
|
goto out;
|
|
|
|
http_client = nmcs_wait_for_objects_register(nm_http_client_new());
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(gtypes); i++) {
|
|
NMCSProvider *provider;
|
|
|
|
provider = g_object_new(gtypes[i], NMCS_PROVIDER_HTTP_CLIENT, http_client, NULL);
|
|
nmcs_wait_for_objects_register(provider);
|
|
|
|
_LOGD("start detecting %s provider...", nmcs_provider_get_name(provider));
|
|
dd.detect_count++;
|
|
nmcs_provider_detect(provider, cancellable, _provider_detect_cb, &dd);
|
|
}
|
|
|
|
if (dd.detect_count > 0)
|
|
g_main_loop_run(main_loop);
|
|
|
|
out:
|
|
nm_clear_g_signal_handler(sigterm_cancellable, &cancellable_signal_id);
|
|
return dd.provider_result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static char **
|
|
_nmc_get_hwaddrs(NMClient *nmc)
|
|
{
|
|
gs_unref_ptrarray GPtrArray *hwaddrs = NULL;
|
|
const GPtrArray * devices;
|
|
char ** hwaddrs_v;
|
|
gs_free char * str = NULL;
|
|
guint i;
|
|
|
|
devices = nm_client_get_devices(nmc);
|
|
|
|
for (i = 0; i < devices->len; i++) {
|
|
NMDevice * device = devices->pdata[i];
|
|
const char *hwaddr;
|
|
char * s;
|
|
|
|
if (!NM_IS_DEVICE_ETHERNET(device))
|
|
continue;
|
|
|
|
if (nm_device_get_state(device) < NM_DEVICE_STATE_UNAVAILABLE)
|
|
continue;
|
|
|
|
hwaddr = nm_device_ethernet_get_permanent_hw_address(NM_DEVICE_ETHERNET(device));
|
|
if (!hwaddr)
|
|
continue;
|
|
|
|
s = nmcs_utils_hwaddr_normalize(hwaddr, -1);
|
|
if (!s)
|
|
continue;
|
|
|
|
if (!hwaddrs)
|
|
hwaddrs = g_ptr_array_new_with_free_func(g_free);
|
|
g_ptr_array_add(hwaddrs, s);
|
|
}
|
|
|
|
if (!hwaddrs) {
|
|
_LOGD("found interfaces: none");
|
|
return NULL;
|
|
}
|
|
|
|
g_ptr_array_add(hwaddrs, NULL);
|
|
hwaddrs_v = (char **) g_ptr_array_free(g_steal_pointer(&hwaddrs), FALSE);
|
|
|
|
_LOGD("found interfaces: %s", (str = g_strjoinv(", ", hwaddrs_v)));
|
|
|
|
return hwaddrs_v;
|
|
}
|
|
|
|
static NMDevice *
|
|
_nmc_get_device_by_hwaddr(NMClient *nmc, const char *hwaddr)
|
|
{
|
|
const GPtrArray *devices;
|
|
guint i;
|
|
|
|
devices = nm_client_get_devices(nmc);
|
|
|
|
for (i = 0; i < devices->len; i++) {
|
|
NMDevice * device = devices->pdata[i];
|
|
const char * hwaddr_dev;
|
|
gs_free char *s = NULL;
|
|
|
|
if (!NM_IS_DEVICE_ETHERNET(device))
|
|
continue;
|
|
|
|
hwaddr_dev = nm_device_ethernet_get_permanent_hw_address(NM_DEVICE_ETHERNET(device));
|
|
if (!hwaddr_dev)
|
|
continue;
|
|
|
|
s = nmcs_utils_hwaddr_normalize(hwaddr_dev, -1);
|
|
if (s && nm_streq(s, hwaddr))
|
|
return device;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
GMainLoop * main_loop;
|
|
NMCSProviderGetConfigResult *result;
|
|
} GetConfigData;
|
|
|
|
static void
|
|
_get_config_cb(GObject *source, GAsyncResult *res, gpointer user_data)
|
|
{
|
|
GetConfigData * data = user_data;
|
|
nm_auto_free_nmcs_provider_get_config_result NMCSProviderGetConfigResult *result = NULL;
|
|
gs_free_error GError *error = NULL;
|
|
|
|
result = nmcs_provider_get_config_finish(NMCS_PROVIDER(source), res, &error);
|
|
|
|
if (!result) {
|
|
if (!nm_utils_error_is_cancelled(error))
|
|
_LOGI("failure to get meta data: %s", error->message);
|
|
} else
|
|
_LOGD("meta data received");
|
|
|
|
data->result = g_steal_pointer(&result);
|
|
g_main_loop_quit(data->main_loop);
|
|
}
|
|
|
|
static NMCSProviderGetConfigResult *
|
|
_get_config(GCancellable *sigterm_cancellable, NMCSProvider *provider, NMClient *nmc)
|
|
{
|
|
nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);
|
|
GetConfigData data = {
|
|
.main_loop = main_loop,
|
|
};
|
|
gs_strfreev char **hwaddrs = NULL;
|
|
|
|
hwaddrs = _nmc_get_hwaddrs(nmc);
|
|
|
|
nmcs_provider_get_config(provider,
|
|
TRUE,
|
|
(const char *const *) hwaddrs,
|
|
sigterm_cancellable,
|
|
_get_config_cb,
|
|
&data);
|
|
|
|
g_main_loop_run(main_loop);
|
|
|
|
return data.result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
_nmc_skip_connection(NMConnection *connection)
|
|
{
|
|
NMSettingUser *s_user;
|
|
const char * v;
|
|
|
|
s_user = NM_SETTING_USER(nm_connection_get_setting(connection, NM_TYPE_SETTING_USER));
|
|
if (!s_user)
|
|
return FALSE;
|
|
|
|
#define USER_TAG_SKIP "org.freedesktop.nm-cloud-setup.skip"
|
|
|
|
nm_assert(nm_setting_user_check_key(USER_TAG_SKIP, NULL));
|
|
|
|
v = nm_setting_user_get_data(s_user, USER_TAG_SKIP);
|
|
return _nm_utils_ascii_str_to_bool(v, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
_nmc_mangle_connection(NMDevice * device,
|
|
NMConnection * connection,
|
|
const NMCSProviderGetConfigResult * result,
|
|
const NMCSProviderGetConfigIfaceData *config_data,
|
|
gboolean * out_skipped_single_addr,
|
|
gboolean * out_changed)
|
|
{
|
|
NMSettingIPConfig * s_ip;
|
|
NMActiveConnection *ac;
|
|
NMConnection * remote_connection;
|
|
NMSettingIPConfig * remote_s_ip = NULL;
|
|
gsize i;
|
|
gboolean addrs_changed = FALSE;
|
|
gboolean rules_changed = FALSE;
|
|
gboolean routes_changed = FALSE;
|
|
gs_unref_ptrarray GPtrArray *addrs_new = NULL;
|
|
gs_unref_ptrarray GPtrArray *rules_new = NULL;
|
|
gs_unref_ptrarray GPtrArray *routes_new = NULL;
|
|
|
|
NM_SET_OUT(out_skipped_single_addr, FALSE);
|
|
NM_SET_OUT(out_changed, FALSE);
|
|
|
|
if (!nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_WIRED_SETTING_NAME))
|
|
return FALSE;
|
|
|
|
s_ip = nm_connection_get_setting_ip4_config(connection);
|
|
if (!s_ip)
|
|
return FALSE;
|
|
|
|
if ((ac = nm_device_get_active_connection(device))
|
|
&& (remote_connection = NM_CONNECTION(nm_active_connection_get_connection(ac))))
|
|
remote_s_ip = nm_connection_get_setting_ip4_config(remote_connection);
|
|
|
|
addrs_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref);
|
|
rules_new =
|
|
g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_routing_rule_unref);
|
|
routes_new = g_ptr_array_new_full(config_data->iproutes_len + !!config_data->ipv4s_len,
|
|
(GDestroyNotify) nm_ip_route_unref);
|
|
|
|
if (remote_s_ip) {
|
|
guint len;
|
|
guint j;
|
|
|
|
len = nm_setting_ip_config_get_num_addresses(remote_s_ip);
|
|
for (j = 0; j < len; j++) {
|
|
g_ptr_array_add(addrs_new,
|
|
nm_ip_address_dup(nm_setting_ip_config_get_address(remote_s_ip, j)));
|
|
}
|
|
|
|
len = nm_setting_ip_config_get_num_routes(remote_s_ip);
|
|
for (j = 0; j < len; j++) {
|
|
g_ptr_array_add(routes_new,
|
|
nm_ip_route_dup(nm_setting_ip_config_get_route(remote_s_ip, j)));
|
|
}
|
|
|
|
len = nm_setting_ip_config_get_num_routing_rules(remote_s_ip);
|
|
for (j = 0; j < len; j++) {
|
|
g_ptr_array_add(
|
|
rules_new,
|
|
nm_ip_routing_rule_ref(nm_setting_ip_config_get_routing_rule(remote_s_ip, j)));
|
|
}
|
|
}
|
|
|
|
if (result->num_valid_ifaces <= 1 && result->num_ipv4s <= 1) {
|
|
/* this setup only has one interface and one IPv4 address (or less).
|
|
* We don't need to configure policy routing in this case. */
|
|
NM_SET_OUT(out_skipped_single_addr, TRUE);
|
|
} else if (config_data->has_ipv4s && config_data->has_cidr) {
|
|
gs_unref_hashtable GHashTable *unique_subnets =
|
|
g_hash_table_new(nm_direct_hash, g_direct_equal);
|
|
NMIPAddress * addr_entry;
|
|
NMIPRoute * route_entry;
|
|
NMIPRoutingRule *rule_entry;
|
|
in_addr_t gateway;
|
|
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
|
|
|
|
for (i = 0; i < config_data->ipv4s_len; i++) {
|
|
addr_entry = nm_ip_address_new_binary(AF_INET,
|
|
&config_data->ipv4s_arr[i],
|
|
config_data->cidr_prefix,
|
|
NULL);
|
|
nm_assert(addr_entry);
|
|
g_ptr_array_add(addrs_new, addr_entry);
|
|
}
|
|
|
|
if (config_data->has_gateway && config_data->gateway) {
|
|
gateway = config_data->gateway;
|
|
} else {
|
|
gateway = nm_utils_ip4_address_clear_host_address(config_data->cidr_addr,
|
|
config_data->cidr_prefix);
|
|
if (config_data->cidr_prefix < 32)
|
|
((guint8 *) &gateway)[3] += 1;
|
|
}
|
|
|
|
for (i = 0; i < config_data->ipv4s_len; i++) {
|
|
in_addr_t a = config_data->ipv4s_arr[i];
|
|
|
|
a = nm_utils_ip4_address_clear_host_address(a, config_data->cidr_prefix);
|
|
|
|
G_STATIC_ASSERT_EXPR(sizeof(gsize) >= sizeof(in_addr_t));
|
|
if (g_hash_table_add(unique_subnets, GSIZE_TO_POINTER(a))) {
|
|
route_entry =
|
|
nm_ip_route_new_binary(AF_INET, &a, config_data->cidr_prefix, NULL, 10, NULL);
|
|
nm_ip_route_set_attribute(route_entry,
|
|
NM_IP_ROUTE_ATTRIBUTE_TABLE,
|
|
g_variant_new_uint32(30200 + config_data->iface_idx));
|
|
g_ptr_array_add(routes_new, route_entry);
|
|
}
|
|
|
|
rule_entry = nm_ip_routing_rule_new(AF_INET);
|
|
nm_ip_routing_rule_set_priority(rule_entry, 30200 + config_data->iface_idx);
|
|
nm_ip_routing_rule_set_from(rule_entry,
|
|
_nm_utils_inet4_ntop(config_data->ipv4s_arr[i], sbuf),
|
|
32);
|
|
nm_ip_routing_rule_set_table(rule_entry, 30200 + config_data->iface_idx);
|
|
nm_assert(nm_ip_routing_rule_validate(rule_entry, NULL));
|
|
g_ptr_array_add(rules_new, rule_entry);
|
|
}
|
|
|
|
rule_entry = nm_ip_routing_rule_new(AF_INET);
|
|
nm_ip_routing_rule_set_priority(rule_entry, 30350);
|
|
nm_ip_routing_rule_set_table(rule_entry, RT_TABLE_MAIN);
|
|
nm_ip_routing_rule_set_suppress_prefixlength(rule_entry, 0);
|
|
nm_assert(nm_ip_routing_rule_validate(rule_entry, NULL));
|
|
g_ptr_array_add(rules_new, rule_entry);
|
|
|
|
route_entry = nm_ip_route_new_binary(AF_INET, &nm_ip_addr_zero, 0, &gateway, 10, NULL);
|
|
nm_ip_route_set_attribute(route_entry,
|
|
NM_IP_ROUTE_ATTRIBUTE_TABLE,
|
|
g_variant_new_uint32(30400 + config_data->iface_idx));
|
|
g_ptr_array_add(routes_new, route_entry);
|
|
|
|
for (i = 0; i < config_data->ipv4s_len; i++) {
|
|
rule_entry = nm_ip_routing_rule_new(AF_INET);
|
|
nm_ip_routing_rule_set_priority(rule_entry, 30400 + config_data->iface_idx);
|
|
nm_ip_routing_rule_set_from(rule_entry,
|
|
_nm_utils_inet4_ntop(config_data->ipv4s_arr[i], sbuf),
|
|
32);
|
|
nm_ip_routing_rule_set_table(rule_entry, 30400 + config_data->iface_idx);
|
|
nm_assert(nm_ip_routing_rule_validate(rule_entry, NULL));
|
|
g_ptr_array_add(rules_new, rule_entry);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < config_data->iproutes_len; ++i)
|
|
g_ptr_array_add(routes_new, config_data->iproutes_arr[i]);
|
|
|
|
addrs_changed = nmcs_setting_ip_replace_ipv4_addresses(s_ip,
|
|
(NMIPAddress **) addrs_new->pdata,
|
|
addrs_new->len);
|
|
|
|
routes_changed = nmcs_setting_ip_replace_ipv4_routes(s_ip,
|
|
(NMIPRoute **) routes_new->pdata,
|
|
routes_new->len);
|
|
|
|
rules_changed = nmcs_setting_ip_replace_ipv4_rules(s_ip,
|
|
(NMIPRoutingRule **) rules_new->pdata,
|
|
rules_new->len);
|
|
|
|
NM_SET_OUT(out_changed, addrs_changed || routes_changed || rules_changed);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
_config_one(GCancellable * sigterm_cancellable,
|
|
NMClient * nmc,
|
|
const NMCSProviderGetConfigResult *result,
|
|
guint idx)
|
|
{
|
|
const NMCSProviderGetConfigIfaceData *config_data = result->iface_datas_arr[idx];
|
|
const char * hwaddr = config_data->hwaddr;
|
|
gs_unref_object NMDevice *device = NULL;
|
|
gs_unref_object NMConnection *applied_connection = NULL;
|
|
guint64 applied_version_id;
|
|
gs_free_error GError *error = NULL;
|
|
gboolean changed;
|
|
gboolean skipped_single_addr;
|
|
gboolean version_id_changed;
|
|
guint try_count;
|
|
gboolean any_changes = FALSE;
|
|
|
|
g_main_context_iteration(NULL, FALSE);
|
|
|
|
if (g_cancellable_is_cancelled(sigterm_cancellable))
|
|
return FALSE;
|
|
|
|
device = nm_g_object_ref(_nmc_get_device_by_hwaddr(nmc, hwaddr));
|
|
if (!device) {
|
|
_LOGD("config device %s: skip because device not found", hwaddr);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!nmcs_provider_get_config_iface_data_is_valid(config_data)) {
|
|
_LOGD("config device %s: skip because meta data not successfully fetched", hwaddr);
|
|
return FALSE;
|
|
}
|
|
|
|
if (config_data->iface_idx >= 100) {
|
|
/* since we use the iface_idx to select a table number, the range is limited from
|
|
* 0 to 99. Note that the providers are required to provide increasing numbers,
|
|
* so this means we bail out after the first 100 devices. */
|
|
_LOGD("config device %s: skip because number of supported interfaces reached", hwaddr);
|
|
return FALSE;
|
|
}
|
|
|
|
_LOGD("config device %s: configuring \"%s\" (%s)...",
|
|
hwaddr,
|
|
nm_device_get_iface(device) ?: "/unknown/",
|
|
nm_object_get_path(NM_OBJECT(device)));
|
|
|
|
try_count = 0;
|
|
|
|
try_again:
|
|
|
|
applied_connection = nmcs_device_get_applied_connection(device,
|
|
sigterm_cancellable,
|
|
&applied_version_id,
|
|
&error);
|
|
if (!applied_connection) {
|
|
if (!nm_utils_error_is_cancelled(error))
|
|
_LOGD("config device %s: device has no applied connection (%s). Skip",
|
|
hwaddr,
|
|
error->message);
|
|
return any_changes;
|
|
}
|
|
|
|
if (_nmc_skip_connection(applied_connection)) {
|
|
_LOGD("config device %s: skip applied connection due to user data %s",
|
|
hwaddr,
|
|
USER_TAG_SKIP);
|
|
return any_changes;
|
|
}
|
|
|
|
if (!_nmc_mangle_connection(device,
|
|
applied_connection,
|
|
result,
|
|
config_data,
|
|
&skipped_single_addr,
|
|
&changed)) {
|
|
_LOGD("config device %s: device has no suitable applied connection. Skip", hwaddr);
|
|
return any_changes;
|
|
}
|
|
|
|
if (!changed) {
|
|
if (skipped_single_addr) {
|
|
_LOGD("config device %s: device needs no update to applied connection \"%s\" (%s) "
|
|
"because there are not multiple IP addresses. Skip",
|
|
hwaddr,
|
|
nm_connection_get_id(applied_connection),
|
|
nm_connection_get_uuid(applied_connection));
|
|
} else {
|
|
_LOGD(
|
|
"config device %s: device needs no update to applied connection \"%s\" (%s). Skip",
|
|
hwaddr,
|
|
nm_connection_get_id(applied_connection),
|
|
nm_connection_get_uuid(applied_connection));
|
|
}
|
|
return any_changes;
|
|
}
|
|
|
|
_LOGD("config device %s: reapply connection \"%s\" (%s)",
|
|
hwaddr,
|
|
nm_connection_get_id(applied_connection),
|
|
nm_connection_get_uuid(applied_connection));
|
|
|
|
/* we are about to call Reapply(). Even if that fails, it counts as if we changed something. */
|
|
any_changes = TRUE;
|
|
|
|
if (!nmcs_device_reapply(device,
|
|
sigterm_cancellable,
|
|
applied_connection,
|
|
applied_version_id,
|
|
&version_id_changed,
|
|
&error)) {
|
|
if (version_id_changed && try_count < 5) {
|
|
_LOGD("config device %s: applied connection changed in the meantime. Retry...", hwaddr);
|
|
g_clear_object(&applied_connection);
|
|
g_clear_error(&error);
|
|
try_count++;
|
|
goto try_again;
|
|
}
|
|
|
|
if (!nm_utils_error_is_cancelled(error)) {
|
|
_LOGD("config device %s: failure to reapply connection \"%s\" (%s): %s",
|
|
hwaddr,
|
|
nm_connection_get_id(applied_connection),
|
|
nm_connection_get_uuid(applied_connection),
|
|
error->message);
|
|
}
|
|
return any_changes;
|
|
}
|
|
|
|
_LOGD("config device %s: connection \"%s\" (%s) reapplied",
|
|
hwaddr,
|
|
nm_connection_get_id(applied_connection),
|
|
nm_connection_get_uuid(applied_connection));
|
|
|
|
return any_changes;
|
|
}
|
|
|
|
static gboolean
|
|
_config_all(GCancellable * sigterm_cancellable,
|
|
NMClient * nmc,
|
|
const NMCSProviderGetConfigResult *result)
|
|
{
|
|
gboolean any_changes = FALSE;
|
|
guint i;
|
|
|
|
for (i = 0; i < result->n_iface_datas; i++) {
|
|
if (_config_one(sigterm_cancellable, nmc, result, i))
|
|
any_changes = TRUE;
|
|
}
|
|
|
|
return any_changes;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
sigterm_handler(gpointer user_data)
|
|
{
|
|
GCancellable *sigterm_cancellable = user_data;
|
|
|
|
if (!g_cancellable_is_cancelled(sigterm_cancellable)) {
|
|
_LOGD("SIGTERM received");
|
|
g_cancellable_cancel(user_data);
|
|
} else
|
|
_LOGD("SIGTERM received (again)");
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
main(int argc, const char *const *argv)
|
|
{
|
|
gs_unref_object GCancellable * sigterm_cancellable = NULL;
|
|
nm_auto_destroy_and_unref_gsource GSource *sigterm_source = NULL;
|
|
gs_unref_object NMCSProvider *provider = NULL;
|
|
gs_unref_object NMClient * nmc = NULL;
|
|
nm_auto_free_nmcs_provider_get_config_result NMCSProviderGetConfigResult *result = NULL;
|
|
gs_free_error GError *error = NULL;
|
|
|
|
_nm_logging_enabled_init(g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_LOG")));
|
|
|
|
_LOGD("nm-cloud-setup %s starting...", NM_DIST_VERSION);
|
|
|
|
if (argc != 1) {
|
|
g_printerr("%s: no command line arguments supported\n", argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
sigterm_cancellable = g_cancellable_new();
|
|
|
|
sigterm_source = nm_g_source_attach(nm_g_unix_signal_source_new(SIGTERM,
|
|
G_PRIORITY_DEFAULT,
|
|
sigterm_handler,
|
|
sigterm_cancellable,
|
|
NULL),
|
|
NULL);
|
|
|
|
provider = _provider_detect(sigterm_cancellable);
|
|
if (!provider)
|
|
goto done;
|
|
|
|
nmc_client_new_waitsync(sigterm_cancellable,
|
|
&nmc,
|
|
&error,
|
|
NM_CLIENT_INSTANCE_FLAGS,
|
|
(guint) NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS,
|
|
NULL);
|
|
|
|
nmcs_wait_for_objects_register(nmc);
|
|
nmcs_wait_for_objects_register(nm_client_get_context_busy_watcher(nmc));
|
|
|
|
if (error) {
|
|
if (!nm_utils_error_is_cancelled(error))
|
|
_LOGI("failure to talk to NetworkManager: %s", error->message);
|
|
goto done;
|
|
}
|
|
|
|
if (!nm_client_get_nm_running(nmc)) {
|
|
_LOGI("NetworkManager is not running");
|
|
goto done;
|
|
}
|
|
|
|
result = _get_config(sigterm_cancellable, provider, nmc);
|
|
if (!result)
|
|
goto done;
|
|
|
|
if (_config_all(sigterm_cancellable, nmc, result))
|
|
_LOGI("some changes were applied for provider %s", nmcs_provider_get_name(provider));
|
|
else
|
|
_LOGD("no changes were applied for provider %s", nmcs_provider_get_name(provider));
|
|
|
|
done:
|
|
nm_clear_pointer(&result, nmcs_provider_get_config_result_free);
|
|
g_clear_object(&nmc);
|
|
g_clear_object(&provider);
|
|
|
|
if (!nmcs_wait_for_objects_iterate_until_done(NULL, 2000)) {
|
|
_LOGE("shutdown: timeout waiting to application to quit. This is a bug");
|
|
nm_assert_not_reached();
|
|
}
|
|
|
|
nm_clear_g_source_inst(&sigterm_source);
|
|
g_clear_object(&sigterm_cancellable);
|
|
|
|
return 0;
|
|
}
|